wiki:PortfileRecipes

Version 18 (modified by ryandesign (Ryan Carsten Schmidt), 14 years ago) (diff)

--

Portfile Recipes

Table of Contents

  1. Branch and major versions
  2. Fetching from a URL that uses GET parameters
  3. Using glob results in "Cannot stat: <list of files>"
  4. variant_isset
  5. default_variants
  6. cvs/svn/git tag for consistency
  7. Don't hardcode /opt/local
  8. Xcode version checking
  9. Stealth updates
  10. Unversioned distfiles
  11. Handling configuration files
  12. Installing additional documentation files
  13. Providing compiler variants

Branch and major versions

Often times, when a port's version is x.y.z, you want to be able to refer to just the x.y part, for a download URL or for other reasons. The de facto standard way to do this is

set branch              [join [lrange [split ${version} .] 0 1] .]

This splits the version string into an array, takes the first two elements of the array, and glues them back together again. This example is from glib2.

If you need to refer just to the x part (the major version) you can similarly do:

set major               [lindex [split ${version} .] 0]

This splits the version string into an array and returns just the first element. This example is from php5.

Fetching from a URL that uses GET parameters

Usually you set master_sites to the URL to the directory you want to download from, and at fetch time MacPorts appends a slash (if necessary) and the distfiles variable to this to create the complete URL. For example, bzip2 says

name                    bzip2
version                 1.0.5
...
homepage                http://www.bzip.org/
master_sites            ${homepage}${version}

so master_sites evaluates to http://www.bzip.org/1.0.5, distfiles is the default bzip2-1.0.5.tar.gz, so the final URL MacPorts will download from is http://www.bzip.org/1.0.5/bzip2-1.0.5.tar.gz.

But sometimes the only download URL available is one that requires GET parameters, for example if you want to download an attachment from a Trac wiki. In this case, list the entire download URL in master_sites, and at the end, leave an empty dummy parameter. MacPorts will still append the slash and the distname, but since it'll be assigned to the dummy GET parameter, the server won't mind. This example is from gtkimageview:

master_sites	${homepage}attachment/wiki/WikiStart/${distfiles}?format=raw&dummy=

Using glob results in "Cannot stat: <list of files>"

glob returns a list which is not handled by some commands (e.g. xinstall); instead, wrap the command in an eval. Instead of

xinstall -m 644 [glob ${worksrcpath}/docs/*.html] ${destroot}${prefix}/share/doc/${name}

which fails with the "Cannot stat..." error, instead use

eval xinstall -m 644 [glob ${worksrcpath}/docs/*.html] ${destroot}${prefix}/share/doc/${name}

variant_isset

Normally you write the code for a variant inside the variant declaration:

variant my_variant description {Variant description} {
    ...
    post-destroot {
        [post-destroot code for when the variant is set]
    }
}

Sometimes it's more natural to include the variant's code in other sections of the Portfile by checking if the variant is (or is not) set:

post-destroot {
    ...
    if {![variant_isset my_variant]} {
        [post-destroot code for when the variant is not set]
    }
}

This is fine, but if you do it this way, you must ensure the variant is still defined in your Portfile, even if it is empty:

variant my_variant description {Variant description} {}

default_variants

Currently negating a variant (through -variant) is not remembered which means a port upgrade will not keep that negation around. This causes issues with default_variants which must be kept in mind (ticket #2377).

The preferred technique is to only select a default variant when one of a set is actually needed; e.g. from ImageMagick, when selecting the pixel quantum:

if {![variant_isset q8] && ![variant_isset q32]} {
    default_variants +q16
}

This also leads to preferring negative-based variants (like no_x11) as using +no_x11 is remembered, whereas if you had x11 as a variant and set it in default_variants, -x11 would not be remembered.

cvs/svn/git tag for consistency

When using one of the checkout-based fetch types (cvs, svn, git, etc) it is best to also use the accompanying tag or date to make sure everyone gets the same checkout as you originally did when creating the port. For example, slime does the following for cvs:

cvs.date        ${version}

irssi-devel does the following for svn:

svn.tag             ${version}

(note that it instead uses svn.revision as svn.tag will be deprecated in favor of svn.revision when MacPorts 1.8 is released).

Don't hardcode /opt/local

Make sure to never hardcode /opt/local anywhere as that is the default prefix for MacPorts, but other ones can be used. Many ports will either use a simple reinplace like

reinplace "s|/usr/local|${prefix}|g" ${worksrcpath}/Makefile

to replace instances of /usr/local with the right MacPorts prefix when a Makefile simply assumes /usr/local (see for example id3v2). Others, when a patch being applied needs to reference the prefix, will use a template for the prefix like @@PREFIX@@, then a similar reinplace:

reinplace "s|@@PREFIX@@|${prefix}|g" ${worksrcpath}/configure

that is then run in post-patch (see for example glib2).

Xcode version checking

MacPorts does not check the version of Xcode being used at runtime, but many ports will fail to build if the version of Xcode is too old. If you discover that your port requires a particular minimum version of Xcode, add this code to the port to print an error if the user's Xcode is too old:

pre-extract {
    if {"darwin" == ${os.platform} && 9 == ${os.major}} {
        set minimum_xcodeversion 3.1
        set current_xcodeversion [exec defaults read /Developer/Applications/Xcode.app/Contents/Info CFBundleShortVersionString]
        if {[rpm-vercomp ${current_xcodeversion} ${minimum_xcodeversion}] < 0} {
            ui_error "On Mac OS X ${macosx_version}, ${name} ${version} requires Xcode ${minimum_xcodeversion} or later but you have Xcode ${current_xcodeversion}."
            return -code error "incompatible Xcode version"
        }
    }
}

This example is from x264. Note that the check is done at pre-extract time.

You can change the "9" in the os.major check to "8" if you're checking Tiger (Xcode 2.x) or "7" for Panther (Xcode 1.x) and update the minimum_xcodeversion accordingly. If you don't know what minimum version of Xcode would work, it's ok to just put whatever version you found that the port worked with. For example, libpixman is known to work with Xcode 1.5 but fail with 1.1, so libpixman now requires Xcode 1.5 on Panther, even though compatibility with Xcode 1.2 was not tested. It is better to make a user upgrade to a newer Xcode than for a port maintainer to spend time testing old versions.

Stealth updates

Some software may update their distfile with new changes without changing the version number (e.g., it stays example-1.2.tar.gz). The safest way to deal with this is to keep the port's version unchanged (e.g., stays at 1.2) while increasing the revision. This however will cause a checksum mismatch for those who already have the previous distfile. The correct solution to this problem is to change dist_subdir. By default, dist_subdir is ${name}; change it so that it includes a subdirectory named for the version and revision:

dist_subdir   ${name}/${version}_${revision}

This example is from the sicp Portfile.

See also the next entry on unversioned distfiles.

Unversioned distfiles

Some software may distribute their distfile with a filename that does not contain the version number (e.g. example.tar.gz); such a port will likely change distname from its default ${name}-${version} to just ${name}. But when updating the port version, the old version's distfile will get in the way, since it has the same name. The correct solution to this problem is to change dist_subdir. By default, dist_subdir is ${name}; change it so that it includes a subdirectory named for the version:

dist_subdir   ${name}/${version}

This example is from the winetricks Portfile.

See also the previous entry on stealth updates.

Handling configuration files

Many ports install config files (usually in ${prefix}/etc) which the user is expected to edit as needed, after the port is installed. Until ticket #2365 is implemented, proper handling of these config files must be done for each port. If you simply install the config file as part of the port itself, it will then be deleted on uninstall, as well as overwritten on upgrade. These outcomes are bad since it loses what the user updated.

The current solution is to rename any config files to append a .dist or .sample extension to them so that the proper name of the config file is not part of the port. Then, in a post-activate phase, the port can test for the file's existence and, if it doesn't exist yet, copy it (copied in post-activate directly in ${prefix} keeps the file from being recorded as part of the port).

The squid Portfile and stegdetect Portfile are good examples.

Installing additional documentation files

Many ports come with documentation files, like "README" and "ChangeLog", which are part of the source but not installed anywhere. If these files are of value to the end user, they should be installed by the port in the post-destroot phase. (Files such as "INSTALL", which contain only installation instructions, are not of value to the end user and should not be installed.)

post-destroot {
    set docdir ${destroot}${prefix}/share/doc/${name}
    xinstall -d ${docdir}
    xinstall -m 644 -W ${worksrcpath} \
        AUTHORS \
        COPYING \
        NEWS \
        README \
        ${docdir}
}

This example is taken from spawn-fcgi.

Note: Ensure you use the correct capitalization for each filename. For example, many projects typically use all-caps for most of the documentation files, except for ChangeLog, which is often written in camel-case. If you fail to use the correct capitalization, the port may still install on your system, but will fail for users with case-sensitive filesystems.

Providing compiler variants

By default, a port will compile using Apple's gcc compiler. For most ports this is fine, but some require a newer version of gcc, or are for some reason incompatible with Apple's version. In these cases you can use configure.compiler to specify an alternate compiler, for example one provided by a MacPorts gcc port. More commonly, a port specifies such a compiler because it needs gcj or gfortran, which Apple does not provide any version of at all.

On the one hand, such ports should prefer to use the newest suitable stable version of gcc they can. On the other hand, a user may already have an older gcc port installed and may not want to spend the time to compile a newer one right now, or may have other reasons for preferring a particular version of gcc. Therefore, ports that need to use a gcc port, but aren't picky about exactly which one, are encouraged to offer variants:

variant gcc42 conflicts gcc43 gcc44 description {Compile with gcc42} {
    configure.compiler macports-gcc-4.2
    depends_lib-append port:gcc42
}

variant gcc43 conflicts gcc42 gcc44 description {Compile with gcc43} {
    configure.compiler macports-gcc-4.3
    depends_lib-append port:gcc43
}

variant gcc44 conflicts gcc42 gcc43 description {Compile with gcc44} {
    configure.compiler macports-gcc-4.4
    depends_lib-append port:gcc44
}

if {![variant_isset gcc42] && ![variant_isset gcc43] && ![variant_isset gcc44]} {
    default_variants +gcc44
}

Note that the variants are all marked as conflicting with one another, and that the newest one is chosen by default if the user has not picked one. Note also that the compiler dependencies are library dependencies because programs compiled using these compilers will generally end up linked to at least one of the compiler's libraries (i.e. libgcc_s.dylib, libgfortran.dylib, etc.).

Setting configure.compiler changes the values MacPorts puts in variables like ${configure.cc}, ${configure.cxx}, ${configure.f77}, etc., which MacPorts automatically sets as environment variables during the configure phase. If the software in question doesn't use the configure phase, and you therefore need to pass these variables to the build phase, you must do so in a pre-build block; if you try to do so directly in the portfile body, you'll pick up the original values, before the variant changed them.

pre-build {
    build.args      CC=${configure.cc} \
                    CXX=${configure.cxx}
}

Another reason to want compiler variants is if the software installs a library, or uses a library built with a different gcc. Subtle and difficult-to-find errors can occur if a library and the program using it are not both compiled with the same compiler.