:!: 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

The above command should help preparing plist now that you are staged.

Make sure you tested make package as a normal user (not root). If that fails, add NEED_ROOT=yes in the port's Makefile.

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

  • 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`

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

:!: 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.

Please add NO_MTREE=yes on meta ports. Otherwise, they would needlessly extract the mtree to the stage dir, and be counted as orphans.

Ports that install kernel modules (usually in /boot/modules) can add USES=kmod to Makefile to have most items taken care of.

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

CategoryHistorical CategoryPorts

  • ports/stagedir.txt
  • Last modified: 2018/05/30 23:55
  • by MateuszPiotrowski