As of 2015, all ports should already have been staged, but you can check for any stragglers on portsmon.
Stage Directory support for ports
The stage directory support means that a port does not install directly into the destination directories, but instead into a separate directory (automake packages call this DESTDIR) from which the package is then built - in many cases, this does not require root privileges. If enabled for a port, the package is first built, installed into the STAGEDIR, packaged, and then installed from the package. If disabled, the traditional approach is used, which means to install directly into the destination and build the package from there.
Convert a port to Stage
List of information to know to be able to convert a port.
Remove the NO_STAGE=yes line.
Makefile {pre,do,post}-install: targets
Prepend ${STAGEDIR} before everything that is a destination
Examples: ${PREFIX}, ${ETCDIR}, ${DATADIR}, ${EXAMPLESDIR}, ${MANPREFIX}, ${DOCSDIR}, etc.
Remove everything that is already handled by @exec entries in pkg-plist, or by the pkg-install script. Duplicating these in both places is no longer needed as the package installation will run them.
- Example: Checking for config.{sample,dist} existence and installation in Makefile.
Remove any code to show pkg-message, it will be shown automatically when the package installs.
Directory creation should remain in the post-install: target (in particular because pkgng doesn't work like pkg_install in that area and pkgng ignores the @exec mkdir, and directly packs the directory even if empty).
Replace commands like ${CHMOD} ... and ${CHOWN}... and ${INSTALL_PROGRAM} -m mode -o user -g group with corresponding pkg-plist entries: @mode mode, @owner user, @group group.
These operators work until being overridden, so do not forget to reset them with @mode, @owner root, @group wheel keywords afterwards.
To set specific mode/user/group for a directory, use these operators before @dirrm(try) directory. For pkg_tools compatibility add: @exec install -d -o user -g group -m mode %D/directory.If you cannot install a port as non-root user, see if adding USES+=uidfix to the port's Makefile helps you. It will override the default user IDs for the ports-framework-based installation commands to the user running them, and the packaging from the stage should then see to proper owner/group settings. See above for how to change them.
The DOCS/EXAMPLES .if blocks can be made unconditional, except in cases when installed files are big enough to cause significant I/O overhead.
When creating a symlink, prepend ${STAGEDIR} to the target path only (and generally try to avoid using absolute paths in source). Example:
${LN} -sf libfoo.so.42 ${STAGEDIR}${PREFIX}/lib/libfoo.so
Note that source of ${PREFIX}/lib/libfoo.so.42, while seemingly okay, could be in fact incorrect, since absolute path can potentially point to a wrong location, e.g. when remote filesystem (with installed package) is mounted via NFS under non-root mount point. Relative paths are less fragile (and often much shorter).
@exec, @unexec ordering in pkg-plist
If there are @exec and @unexec that deal with installed files, for instance, copying a sample file to a configuration in @exec and removing it - if unaltered - from @unexec, the @unexec must be before and the @exec must be after the affected file. Example:
@comment ### THIS IS WRONG ### @exec if [ ! -f %D/etc/dnsmasq.conf ]; then cp -p %D/%F %B/dnsmasq.conf; fi @unexec if cmp -s %D/etc/dnsmasq.conf %D/etc/dnsmasq.conf.example ; then rm -f %D/etc/dnsmasq.conf ; fi etc/dnsmasq.conf.example
must be converted to:
@unexec if cmp -s %D/etc/dnsmasq.conf %D/etc/dnsmasq.conf.example ; then rm -f %D/etc/dnsmasq.conf ; fi etc/dnsmasq.conf.example @exec if [ ! -f %D/etc/dnsmasq.conf ]; then cp -p %D/%F %B/dnsmasq.conf; fi
PORTDOCS PORTEXAMPLES
- To support PORTDOCS and PORTEXAMPLES, most of the time, there is no more need for hacks. Just add the right %%PORTDOCS%% or %%PORTEXAMPLES%% as prefix in your pkg-plist, then the given files from the stagedir will or will not become part of the package according to DOCS and EXAMPLES port options.
MAN*/MANLANG/MLINKS now useless
- manpage compression/uncompression is now automatically handled by the framework if you use stagedir.
When converting remove the MAN*, MANLANG and MLINKS and add the man pages as any normal files in pkg-plist.
Conversion script:
{ for section in $(jot 9); do for file in $(make -V MAN${section}); do echo man/man${section}/${file}.gz; done; done; MLINKS=$(make -V MLINKS); set -- ${MLINKS}; while :; do [ $# -eq 0 ] && break; file=$2; section=${file##*.}; echo man/man${section}/${file}.gz; shift 2; done; }|sort
Alternative conversion script:
make -V __MANPAGES -V _MLINKS | tr ' ' '\n' | sed -e 's=/usr/local/==g' | sort -u
Simpler conversion script if port does not use MLINKS:
make -V __MANPAGES | tr ' ' '\n' | sort
make makeplist
The above command should help preparing plist now that you are staged.
Package as an unprivileged user
Make sure you tested make package as a normal user (not root). If that fails, add NEED_ROOT=yes in the port's Makefile.
Python scripts reference work or stage directory (in a complaint from the staging Q/A checks)
If this happens when the upstream software you are packaging claims DESTDIR support, it is broken (Mailman up to 2.1.16, for instance).
Barring upstream fixes, you can work around the problem, and recompile scripts like this, for instance, from a post-build: target. Assuming your Python scripts are supposed to reside in ${PYTHONPREFIX_SITELIBDIR} after installation:
(cd ${STAGEDIR}${PREFIX} \ && ${PYTHON_CMD} ${PYTHON_LIBDIR}/compileall.py \ -d ${PYTHONPREFIX_SITELIBDIR} -f ${PYTHONPREFIX_SITELIBDIR:S;${PREFIX}/;;})
This runs a compilation on a path relative to the stage directory (based on the cd ${STAGEDIR}${PREFIX} part), and prefixes the ${PYTHONPREFIX_SITELIBDIR} to the file name recorded in the byte-compiled output file (through the -d ${PYTHONPREFIX_SITELIBDIR}). -f is required to force recompilation, and the :S;${PREFIX}/;; is to strip the prefix from the PYTHONPREFIX_SITELIBDIR variable to make it a path relative to ${PREFIX}.
TIPS and TRICKS
Confirming your StageDir conversion work
env DEVELOPER=yes make stage && make check-plist && make package
- poudriere testport -n -o category/portname (requires poudriere 3.0.7+ or 3.0.99.20130923.1+)
- Note that poudriere supports checking for staging orphans and tinderbox does not. It will find issues that tinderbox will not report.
This will soon be enabled in tinderbox -BryanDrewery
test your files for stagedir, the stagedir should no appear other than in Makefiles:
grep -r work/stage `make -V WRKSRC`
p5-* pkg-plist and MAN
For p5-ports which previously use MAN3 you can use macro PERL5_MAN3 inside pkg-plist. For example: lib/perl5/5.14/man/man3/AnyEvent::I3.3.gz can be replaced with %%PERL5_MAN3%%/AnyEvent::I3.3.gz
@cwd
Beware: pkg_install is buggy when handling "@cwd", if your ports have "@cwd" in the plist it needs to be changed to the actual directory. Using @cwd %%PREFIX%% will not work as it is already in PLIST_SUB as PREFIX=%D.
A workaround is to use @cwd %%RESETPREFIX%% in pkg-plist.
META ports
Please add NO_MTREE=yes on meta ports. Otherwise, they would needlessly extract the mtree to the stage dir, and be counted as orphans.
Kernel module ports
Ports that install kernel modules (usually in /boot/modules) can add USES=kmod to Makefile to have most items taken care of.
ranlib error (permission denied)
Makefile.in created with old automake runs ranlib twice, when building the library and when installin (on our case, staging), and because of that you can see errors like these:
Making install in lib /bin/sh ../mkinstalldirs .../work/stage/usr/local/lib install -m 444 libft.a .../work/stage/usr/local/lib/libft.a /usr/local/bin/ranlib .../work/stage/usr/local/lib/libft.a /usr/local/bin/ranlib: unable to copy file '.../work/stage/usr/local/lib/libft.a'; reason: Permission denied
A workaround to get it fixed is to add a REINPLACE_CMD on post-patch: target to remove RANLIB from installation, like this:
@${REINPLACE_CMD} -e '/echo.*RANLIB/,+1d' \ ${WRKSRC}/lib/Makefile.in
Known problems
- python easy_install