NAME Module::Build::SysPath - install files to system folders according to FHS (or Sys::Path settings) SYNOPSIS use Module::Build::SysPath; my $builder = Module::Build::SysPath->new( ... DESCRIPTION A subclass of Module::Build using Sys::Path to determine the system folders. Help in task of installing files into system folders and keeping the option to work in local distribution files while developing the module. See Acme::SysPath for example usage of a module that needs a configuration and a folder to store templates in. USAGE module-starter module-starter --builder=Module::Build --module=Acme::NewModule --author="Pod" --email=pod@pod cd Acme-NewModule/ perl -lane 's/Module::Build-/Module::Build::SysPath-/; print $_;' -i Build.PL vim Build.PL # s/Module::Build-/Module::Build::SysPath-/ # add "configure_requires => { 'Module::Build::SysPath' => 0.10 }," # add "Module::Build::SysPath' => 0.10," to build_requires create SPc.pm copy and add it to your source tree. Clean up the paths that you don't need. Local distribution folder names can be changed to anyones taste. For example: sub sysconfdir { File::Spec->catdir(__PACKAGE__->prefix, 'conf') }; 'conf' is the name of a folder with "conffile"s. All file put to this folder will be installed to Sys::Path->sysconfdir(). use the SPc.pm Calling "Acme::NewModule::SPc->sysconfdir" before the distribution is installed will return path to the 'conf' folder in the distribution root folder. Calling it after install the distribution will return Sys::Path->sysconfdir(). EXAMPLE See Acme::SysPath for a really simple, Test::Daily for a real world example. new Populates: $builder->{'properties'}->{$path_type.'_files'} = ...; $builder->{'properties'}->{'install_path'}->{$path_type} = ...; $builder->add_build_element($path_type); To install files located in: sysconfdir datadir docdir localedir webdir srvdir Folders in: cachedir logdir spooldir rundir lockdir sharedstatedir are skipped during the installation. Add .exists to this folders if you want them to be created during `./Build install`. The .exists file itself is not installed. To install a specific file from a skipped folder, a file from a folder starting with a dot, or a file starting with a dot, list its path relative to the distribution root in the "_files" property: my $builder = Module::Build::SysPath->new( ... sharedstatedir_files => { 'var/lib/acme-notes/index.json' => 1, }, webdir_files => { 'www/.htaccess' => 1, }, ); Every "./Build install" overwrites such a file with the distribution version, so runtime changes to it are lost. To keep them, also list the file in "conffiles", or create the file at runtime instead of shipping it. MANIFEST When the distribution root contains a MANIFEST, only files listed in it are installed. "perl Build.PL" warns about every other file it would have installed: 'etc/acme/secret.pem' is not listed in MANIFEST and will not be installed Files matching MANIFEST.SKIP (or the default skip list of ExtUtils::Manifest when there is no MANIFEST.SKIP) are left out on purpose, so they are not installed and not reported. Files kept out of the tarball by MANIFEST.SKIP are not installed from a checkout either. If a checkout install must deploy such files, for example keys that should not be published, copy them in a custom "ACTION_install" or deploy them outside the build. This keeps editor backups and other stray files in a checkout from being installed, and makes an install from a checkout match an install from the distribution tarball. Without a MANIFEST, every file found is installed. Rerun "perl Build.PL" after adding files. The file list is collected from the folders on disk when "perl Build.PL" runs, not when "./Build install" runs. Configuration files Configuration files get a special (Debian like) treatment. All files in "sysconfdir" and all files specified as "$builder->{'properties'}->{'conffiles'}" are configuration files. Using "install_checksums" in Sys::Path the cs checksums are tracked. Here are the model situations: "conffile" was never installed jet The file is just copied in place (to sysconfdir) as it is. MD5 is recorded. distribution ships new version, no change in system The distribution changed the "conffile" (for example by adding new values), but the "conffile" was untouched in the system. Then the new version from distribution replaces the one in the system. distribution "conffile" wasn't changed, "conffile" changed in system Already installed distribution is getting upgrade. Distribution "conffile"s form installed and the new version didn't change. But the "conffile" was changed in the system. No prompt and the "conffile" is kept intact. distribution "conffile" change, "conffile" changed in system Already installed distribution is getting upgrade. When both the distribution changed the "conffile" and the "conffile" was changed in the system. User will be prompted what to do: Installing new version of config file /etc/SOMEFILE ... Configuration file `/etc/SOMEFILE' ==> Modified (by you or by a script) since installation. ==> Package distributor has shipped an updated version. What would you like to do about it ? Your options are: Y or I : install the package maintainer's version N or O : keep your currently-installed version D : show the differences between the versions Z : background this process to examine the situation The default action is to keep your current version. *** /etc/SOMEFILE (Y/I/N/O/D/Z) ? If N or O is selected distribution files is installed with -spc suffix. If Y or I is selected the system "conffile" is renamed by adding suffix -old and distribution "conffile" is installed. Installation aborts without changing either file if the -old backup already exists. If the parent installation fails after the rename, the original "conffile" is restored. Unattended installs take the default answer N: the system "conffile" is kept and the distribution version is installed with the -spc suffix. An install is unattended with "PERL_MM_USE_DEFAULT=1", or without a terminal when standard input is at end of file. A system "conffile" without a recorded checksum counts as changed in the system. File modes Set the permissions of installed files with "file_modes". Keys are paths relative to the distribution root, values are modes written as Perl octal literals: my $builder = Module::Build::SysPath->new( ... file_modes => { 'etc/acme/secret.ini' => 0640, }, ); Write the mode unquoted. A quoted '0640' would be read as a decimal number, so quoted modes with a leading zero are rejected. Every key must name a single file that is already selected for installation. "file_modes" does not select files: it neither bypasses MANIFEST nor replaces "_files". A "file_modes" value that is not a hash, keys naming unselected files, the same file twice (for example etc/x and ./etc/x), directories, globs, .exists markers, absolute paths or paths leaving the distribution root, and modes outside 0000..0777 or non-integer modes make "perl Build.PL" die. A declared mode is a ceiling. It never grants access that the file in the system does not already have: new file Gets the declared mode. existing file Gets the declared mode combined (bitwise AND) with its current mode. With 0640 declared, a 0644 file becomes 0640 and a 0600 file stays 0600. This also applies to a kept "conffile", its -spc copy and an -old backup, whose current mode is the mode of the replaced "conffile", so a 0644 original becomes a 0640 backup. A new -spc copy is limited only by an existing -spc, not by the mode of the kept "conffile". "--destdir" The same rules apply to the staged files under the destination directory. Files in the live system are not touched. Without a declaration, files installed to "sysconfdir" and "sharedstatedir" are set to 0644, and other files keep the mode set by ExtUtils::Install (0444, or 0555 for executables). If setting a declared mode fails, the installation dies with the path and mode, and no "conffile" checksums are recorded. The declared mode is guaranteed only after a successful installation. ExtUtils::Install first installs every file with mode 0444 (0555 for executables), and "ACTION_install" sets the declared mode afterwards. Until then, and after a failed installation, a secret file can be readable by every local user who can reach its folder. Deployments that need stronger guarantees should install through a distribution package that sets permissions while copying. Owner and group Installation never assigns an owner or group of its own. A new file belongs to the user running the installation. When a file that already exists in the system is replaced, the new file gets the owner and group of the replaced file, so a "chgrp" of a secret "conffile" to the group of a service survives upgrades. This covers payload files of every path type, the -spc copy of a kept "conffile" and, when the new version of a "conffile" is accepted, the newly installed "conffile". A kept "conffile" and an -old backup are not replaced, so they keep their owner and group. With "--destdir", the same applies to the staged files. Only root can give a file to another user. If restoring the owner or group fails, the installation warns with the path and continues. ACTION_install This action is responsible for renaming files, replacing SPc.pm paths to systems once from Sys::Path. Also restores the owner and group of replaced files as described in "Owner and group", sets undeclared "sysconfdir" and "sharedstatedir" files to 0644, applies "file_modes" as described in "File modes", and stores the checksums of "conffile"s. AUTHOR Jozef Kutej, CONTRIBUTORS The following people have contributed to the Sys::Path by commiting their code, sending patches, reporting bugs, asking questions, suggesting useful advices, nitpicking, chatting on IRC or commenting on my blog (in no particular order): Lars Dɪᴇᴄᴋᴏᴡ 迪拉斯 Emmanuel Rodriguez Slaven Rezić COPYRIGHT & LICENSE Copyright 2009 Jozef Kutej, all rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.