wiki:PortfileRecipes

Version 35 (modified by ryandesign (Ryan Carsten Schmidt), 12 years ago) (diff)

rewrite gcc-4.2 compiler selection block

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. Removing -arch flags from *-config scripts, *.pc files, etc.
  14. Specifying which compiler to use based on the version of Xcode
  15. Providing compiler variants
  16. Replacing and renaming ports

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

When some users may want to turn a feature off but most will want it on, you can use a variant that is on by default. Define the variant as usual, and then use the default_variants keyword like this (to make the foo variant on by default):

default_variants +foo

The port will then be installed with +foo unless the user specifies -foo on the command line or in variants.conf.

You can also use default_variants when one of a set of mutually exclusive variants is needed; e.g. from ImageMagick, when selecting the pixel quantum:

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

Formerly, use of negatively-named variants (like no_x11) was preferred to having a default positively-named variant (like x11), due to a deficiency in the port registry (ticket #2377). Now that this is fixed, use of negatively-named variants should be avoided.

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.revision    ${version}

Don't hardcode /opt/local

Make sure to never hardcode /opt/local anywhere as that is only the default prefix for MacPorts; a different prefix 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 checks the version of Xcode being used at runtime and warns if it is an old version known to cause problems, but for specific ports that are known to fail to build with older versions of Xcode, you may want to actively prevent the installation from continuing, rather than just printing a warning. If you discover that your port requires a particular minimum version of Xcode, use the xcodeversion portgroup to print an error and exit if the user's Xcode is too old. At the top of the portfile, after the PortSystem line and near any other PortGroup lines you may have, add this line:

PortGroup               xcodeversion 1.0

Then somewhere in the body of the portfile you can write a line like this:

minimum_xcodeversions {9 3.1}

This example is from x264 and ensures that, on Darwin 9 (Mac OS X 10.5 Leopard), Xcode 3.1 or greater is used.

If you need to check the minimum Xcode version of more than one OS version, simply add more pairs of arguments. For example, to check for a minimum of Xcode 3.1 on Darwin 9 and a minimum of Xcode 2.5 on Darwin 8 (Mac OS X 10.4 Tiger), you would write:

minimum_xcodeversions {9 3.1 8 2.5}

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 if someone reports a problem building a port on Leopard with Xcode 3.0, and you verified it works fine with Xcode 3.1.4, it's ok to list 3.1.4 as the minimum Xcode version, even without testing whether earlier 3.1.x versions would have worked. 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

Although they would be wise not to do this, some software developers occasionally update their distfile with new changes without changing the distfile's name (e.g., it stays example-1.2.tar.gz). The port's checksums then need to be updated, but the version stays the same (e.g. stays at 1.2). So that users will see the update, the revision is increased, but users who already have the previous distfile would then experience a checksum mismatch. To avoid this, you must change dist_subdir. By default, dist_subdir is ${name}; change it so that it includes a subdirectory named for the version number and the distfile revision number (start at 1 and increment by 1 each time this version's distfile is stealth-updated):

dist_subdir   ${name}/${version}_1

This line should be removed when the next proper version of the software is released. It's a good idea to add a comment above the dist_subdir line reminding yourself (or the next committer) to do this.

Using the ${revision} variable in the dist_subdir definition isn't usually a good idea, because the revision is supposed to allow the portfile author to offer the user an upgrade that is unrelated to the upstream software version.

Compare the old distfile with the new one. (In most cases, the old distfile can be downloaded from the MacPorts distfiles mirror.) Sometimes the new distfile does not differ materially from the old one (e.g. the gz or bz2 files are different but the underlying tar files are identical, or there are only changes in files that the port does not install). If you can determine that this has happened, then you don't want to force users to rebuild since it would be of no benefit to them. In this case, do not increase the port's revision, but do update the checksums and add the dist_subdir line as above.

For software whose developers habitually stealth-update their software, further measures may need to be taken, such as those employed by the dcraw and molden ports. But ideally, convince the developers to refrain from stealth-updating. Any time a distfile is released to their download area, they should consider it to be immutable.

See also the next entry on unversioned distfiles.

Unversioned distfiles

Although they would be wise not to do this, some software developers may distribute their distfile with a filename that does not contain the version number (e.g. example.tar.gz); such a port will have its distname specified as ${name} rather than its default ${name}-${version}. But when updating the port to a new version, the old version's distfile will get in the way, since it has the same name, and users who had a previous version of the port installed will experience a checksum mismatch. To avoid this, you must 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}

Ideally, however, convince the developers to put the version number in their distfile names. Any time a distfile is released to their download area, they should consider it to be immutable.

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/${subport}
    xinstall -d ${docdir}
    xinstall -m 644 -W ${worksrcpath} \
        AUTHORS \
        COPYING \
        NEWS \
        README \
        ${docdir}
}

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.

Note: This recipe used to recommend using ${destroot}${prefix}/share/doc/${name}, which is fine if used in a standalone port, but this will cause conflicts if used in a port with subports, such as one using the unified python portgroup.

Removing -arch flags from *-config scripts, *.pc files, etc.

MacPorts provides -arch flags to ports' configure scripts in CFLAGS, LDFLAGS and similar variables, which tell the port what architecture(s) to build for, but these -arch flags should not appear in *-config scripts or pkg-config *.pc files or the like because they will influence the build of dependent ports.

Consider: libetpan @0.58_0+universal is installed. Its libetpan-config script contains -arch flags. I now want to build etpan which depends on libetpan. etpan cannot be built universal so I do not request the +universal variant when building etpan. Yet it still tries to build universal because it got the universal -arch flags from libetpan's config script, so the build fails.

Port maintainers should therefore ensure that any -arch flags are removed from such scripts and files prior to installation, e.g. like this:

post-configure {
    reinplace -E {s|-arch [a-z0-9_]+||g} \
        ${worksrcpath}/redland-src-config \
        ${worksrcpath}/redland.pc
}

This example is taken from redland.

If you're using the muniversal portgroup, it's a bit more complicated because you have to reinplace in each architecture's source directory, and you have to still accommodate non-universal builds:

post-configure {
    if {[variant_isset universal]} {
        set dirs {}
        foreach arch ${universal_archs_to_use} {
            lappend dirs ${worksrcpath}-${arch}
        }
    } else {
        set dirs ${worksrcpath}
    }
    foreach dir ${dirs} {
        reinplace -E {s|-arch [a-z0-9_]+||g} \
            ${dir}/curl-config \
            ${dir}/libcurl.pc
    }
}

This example is taken from curl.

If you modify a port to remove -arch flags from its *-config scripts and/or *.pc files, don't forget to also increase the port's revision (unless you're already increasing its version).

Specifying which compiler to use based on the version of Xcode

As of MacPorts 2, the default value of configure.compiler is chosen based on Xcode version instead of OS X version. If a certain compiler won't build your port, test whether that compiler has been chosen, and then set a different compiler that does work. When choosing an alternate compiler, keep in mind that as of Xcode 4, we would prefer ports build with clang. If that's not possible, use llvm-gcc-4.2. If neither work, use gcc-4.2, but provide a fallback to apple-gcc-4.2.

If your port works with clang but not llvm-gcc-4.2, do this:

if {${configure.compiler} == "llvm-gcc-4.2"} {
    configure.compiler clang
}

If your port works with llvm-gcc-4.2 but not clang, do this:

if {${configure.compiler} == "clang"} {
    configure.compiler llvm-gcc-4.2
}

These snippets are taken from this message on macports-dev.

If your port works with neither clang nor llvm-gcc-4.2, use gcc-4.2; since on Xcode 4.2 and up (i.e. where the default compiler is clang) there is no included version of gcc-4.2, use apple-gcc-4.2 in that case. Use of this block should be considered a temporary stopgap measure. Please file a bug report with the upstream developers of the software so they can fix it to work with clang and llvm-gcc-4.2. Put a reference to the upstream bug report in the Portfile.

if {${configure.compiler} == "llvm-gcc-4.2"} {
   configure.compiler gcc-4.2
} elseif {${configure.compiler} == "clang"} {
   depends_build-append port:apple-gcc42
   configure.compiler apple-gcc-4.2
   # base (as of 2.0.3) doesn't set cxx for apple-gcc-4.2
   configure.cxx ${prefix}/bin/g++-apple-4.2
}

This snippet is taken from this message on macports-dev.

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 gcc43 conflicts gcc44 gcc45 description {Compile with gcc 4.3} {
    configure.compiler macports-gcc-4.3
    depends_lib-append port:gcc43
}

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

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

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

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.

Replacing and renaming ports

When a software project is renamed, a new port is created with the project's new name. The new port should usually not be created from scratch; instead it should be copied from the old port, using the svn copy command, to preserve the port's Subversion history. For example, when the developers of the OSXvnc project renamed it to Vine Server, the new vineserver port was created by copying the old osxvnc port:

cd $(port dir osxvnc)/../
svn copy osxvnc vineserver

Then the new port (vineserver, in this example) is edited, including changing the name field to the project's new name, most likely changing the version, possibly changing the homepage, master_sites and livecheck, and anything else related to the name change. It's also a good idea to include the project's old name in the new port's description and long_description so that users searching for it by its old name can find it.

The old port (osxvnc) needs to be kept around for some time, and converted into a stub port marked as having been replaced by the new port, so that users who already had the old port installed will learn about the new port and will be prompted (via port outdated) to upgrade to it. There are several steps to this process:

  1. Add the replaced_by line, indicating the name of the port by which this port has been replaced, for example replaced_by vineserver. This causes MacPorts, when running sudo port upgrade osxvnc, to deactivate osxvnc and to install vineserver in its place. In order for osxvnc to appear in port outdated in the first place, however, its version, revision or epoch must be increased, so:
  2. Increase the port's version, revision or epoch. Customarily, the old port's version is set to the version of the replacement port. So, when osxvnc @3.0_1 was replaced by vineserver @3.1_0, osxvnc's version was set to 3.1 and its revision dropped to 0 to match the new vineserver port. If the software was only renamed, without changing its version number, then increase the old port's revision.
  3. The above handles upgrades for users who already had the port installed, but does not prevent users from actually installing the old port. To prevent that, there should be a pre-configure block informing the user of the replacement and terminating the installation attempt; see the complete Portfile example below. Note that this should be a pre-configure block, not a pre-fetch block, because some users like to fetch all outdated ports' distfiles using sudo port fetch outdated (for example because they temporarily have access to a faster network connection) and we don't want to interrupt that.
  4. Remove all directives related to fetching and verifying files: master_sites, patch_sites, checksums, fetch.*, cvs.*, svn.*, hg.*, git.*, or bzr.* directives. Set just a single directive distfiles to indicate there are no distfiles to fetch.
  5. Remove any existing directives and blocks for any of the phases: fetch, extract, patch, configure, build, destroot, install, archive, activate and deactivate (other than the pre-configure block added above).
  6. Remove any depends_*, PortGroup and notes directives and any variant and platform blocks.
  7. Remove the files directory if present since those files are no longer needed.
  8. Since the old port will no longer be updated, set livecheck.type none.
  9. Optionally, update the description and long_description to indicate the port has been replaced.

Here is a complete example stub port:

# -*- coding: utf-8; mode: tcl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- vim:fenc=utf-8:ft=tcl:et:sw=4:ts=4:sts=4
# $Id$

PortSystem          1.0

name                osxvnc
replaced_by         vineserver
version             3.1
categories          aqua vnc
platforms           darwin
maintainers         ryandesign
license             GPL-2+

description         a full-featured VNC server (formerly OSXvnc)

long_description    Vine Server (formerly OSXvnc) is a full-featured VNC \
                    server for Mac OS X providing remote access to the GUI, \
                    keyboard and mouse using any VNC client.

homepage            http://www.testplant.com/products/vine_server

distfiles

pre-configure {
    ui_error "${name} has been renamed to ${replaced_by}. Please install ${replaced_by} instead."
    return -code error "obsolete port"
}

livecheck.type      none

The reason for leaving a stub port (rather than deleting the old port immediately) is to provide a seamless upgrade process for people who installed the port under its old name. So the stub port should not be deleted until after most users can be expected to have used sudo port selfupdate and sudo port upgrade outdated. Since some users do not update frequently, it is recommended that the old port remain for no less than one year after the replaced_by line is added.

Sometimes a port is replaced not because the software project was renamed, but because its functionality was rolled into another existing project. (For example, the functionality of the glut port was rolled into the mesa port.) The same process applies, except that the wording of the ui_error should be changed from "renamed to" to "replaced by".

pre-configure {
    ui_error "${name} has been replaced by ${replaced_by}; please install ${replaced_by} instead."
    return -code error "obsolete port"
}

This also applies in case a -devel port was created but will no longer be updated and is being replaced by a corresponding non-devel port. (For example, the xz-devel port was replaced by the xz port.)