# Portfile Development

This chapter covers a brief introduction to Portfiles, how to create a local Portfile repository for development, and creating Portfiles.

## Portfile Introduction

A MacPorts Portfile is a TCL script that usually contains only the simple keyword/value combinations and Tcl extensions as described in the Portfile Reference chapter, though it may also contain arbitrary TCL code. Every port has a corresponding Portfile, but Portfiles do not completely define a port's installation behavior since MacPorts base has default port installation characteristics coded within it. Therefore Portfiles need only specify required options, though some ports may require non-default options.

A common way for Portfiles to augment or override MacPorts base default installation phase characteristics is by using `Portfile` phase declaration(s). If you use Portfile phase declaration(s), you should know how to identify the "global" section of a Portfile. Any statements not contained within a phase declaration, no matter where they are located in a Portfile, are said to be in the global section of the Portfile; therefore the global section need not be contiguous. Likewise, to remove statements from the global section they must be placed within a phase declaration.

The main phases you need to be aware of when making a Portfile are these:

- Fetch

- Extract

- Patch

- Configure

- Build

- Destroot

The default installation phase behavior performed by the MacPorts base works fine for applications that use the standard `configure`, `make`, and `make install` steps, which conform to phases configure, build, and destroot respectively. For applications that do not conform to this standard behavior, any installation phase may be augmented using pre- and/or post- phases, or even overridden or eliminated. See Example Portfiles below.

For a detailed description of all port phases, see the Portfile Reference chapter.

## Creating a Portfile

Here we list the individual Portfile components for an application that conforms to the standard `configure`, `make`, and `make install` steps of most open source application installs.

1. Modeline

   This should be the first line of a Portfile. It sets the correct editing options for vim and emacs. See Port Style for more information. Its use is optional and up to the port maintainer.

       # -*- 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

2. Subversion ID tag line

   This must be a Portfile's second line (or the first, if a modeline is not used). When a port is committed to the repository, ID tags are expanded to include the filename and the revision number, date and time, and author of the last commit.

       # $Id$

3. PortSystem line

   This statement is required for all ports.

       PortSystem          1.0

4. Port name

       name                rrdtool

5. Port version

       version             1.2.23

6. Port categories

   A port may belong to more than one category, but the first (primary) category should match the directory name in the ports tree where the Portfile is to reside.

       categories          net

7. Port maintainers

   A port's maintainer is a person or persons who take responsibility for keeping the port up-to-date, and the maintainer keyword lists maintainer email addresses(s). To hide these addresses from spambots, see the more full explanation of the maintainer keyword in the Global Keywords section of the Portfile Reference chapter.

       maintainers         julesverne@example.org

   The address nomaintainer@macports.org, or in hidden form nomaintainer, designates a port that may be modified by any committer.

8. Port description

       description         Round Robin Database

9. Port long_description

       long_description    RRDtool is a system to store and display time-series \
                           data

10. A port's application homepage

        homepage            http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/

11. Platform statement

        platforms           darwin

12. A port's download URLs

        master_sites        http://oss.oetiker.ch/rrdtool/pub/ \
                            ftp://ftp.pucpr.br/rrdtool/

13. Port checksums

    The checksums specified in a Portfile are checked with the fetched tarball for security. For the best security, use md5, sha1, and rmd160 checksum types.

        checksums           md5 dafa161bc9c61e57636a6085c87c1fe8 \
                            sha1 5da610e1c8bc01b80abc21ab9e98e004363b429c \
                            rmd160 0c1147242adf476f5e93f4d59b553ee3ea378b23

    To find the correct checksums for a port's distribution file, follow this example:

        %% md5 rrdtool-1.2.23.tar.gz
        %% openssl sha1 rrdtool-1.2.23.tar.gz
        %% openssl rmd160 rrdtool-1.2.23.tar.gz

        MD5 ( ... rrdtool-1.2.23.tar.gz) = dafa161bc9c61e57636a6085c87c1fe8

        SHA1( ... rrdtool-1.2.23.tar.gz)= 5da610e1c8bc01b80abc21ab9e98e004363b429c

        RIPEMD160( ... rrdtool-1.2.23.tar.gz)= 0c1147242adf476f5e93f4d59b553ee3ea378b23

14. Port dependencies

    A port's dependencies are ports that must be installed before another port is installed.

        depends_lib         port:perl5.8 \
                            port:tcl \
                            port:zlib

15. Port configure arguments (optional)

        configure.args      --prefix=${prefix} \
                            --enable-perl-site-install \
                            --mandir=${prefix}/share/man

## Example Portfiles

In this section we begin by taking a look at a complete simple Portfile; then we see how to augment default phases by defining pre- and post- phases, how to override default phases, and finally how to eliminate port phases.

### A Basic Portfile

    # $Id$

    PortSystem          1.0

    name                rrdtool
    version             1.2.23
    categories          net
    maintainers         julesverne
    description         Round Robin Database
    long_description    RRDtool is a system to store and display time-series data
    homepage            http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/
    platforms           darwin
    master_sites        http://oss.oetiker.ch/rrdtool/pub/ \
                        ftp://ftp.pucpr.br/rrdtool/

    checksums           md5 dafa161bc9c61e57636a6085c87c1fe8 \
                        sha1 5da610e1c8bc01b80abc21ab9e98e004363b429c \
                        rmd160 0c1147242adf476f5e93f4d59b553ee3ea378b23

    depends_lib         port:perl5.8 \
                        port:tcl \
                        port:zlib

    configure.args      --enable-perl-site-install \
                        --mandir=${prefix}/share/man

### Augment Phases Using pre- / post-

To augment a port's installation phase, and not override it, you may use pre- and post- installation phases as shown in this example.

    post-destroot {
        # Install example files not installed by the Makefile
        file mkdir ${destroot}${prefix}/share/doc/${name}/examples
        file copy ${worksrcpath}/examples/ \
            ${destroot}${prefix}/share/doc/${name}/examples
    }

### Overriding Phases

To override the automatic MacPorts installation phase processing, define your own installation phases as shown in this example.

    destroot {
        xinstall -m 755 -d ${destroot}${prefix}/bin
        xinstall -m 755 ${worksrcpath}/cdpr ${destroot}${prefix}/bin
    }

### Eliminating Phases

To eliminate a default phase, simply define a phase with no contents as shown.

    build {}

Because many software packages do not use `configure`, a keyword is provided to eliminate the `configure` phase. See the chapter Portfile Reference for full information.

### Creating a StartupItem

Startupitems may be placed in the global section of a Portfile.

    startupitem.create      yes
    startupitem.name        nmicmpd
    startupitem.executable  "${prefix}/bin/nmicmpd"

Startupitems keywords may also be used within a variant definition to make their installation conditional.

    variant server {
        startupitem.create  yes
        startupitem.start   "${prefix}/share/${name}/vm-pop3d.init start"
        startupitem.stop    "${prefix}/share/${name}/vm-pop3d.init stop"
    }

## Port Variants

Variants are a way for port authors to provide options that may be invoked at install time. They are declared in the global section of a Portfile using the "variant" keyword, and should include carefully chosen variant descriptions.

### Example Variants

The most common actions for user-selected variants is to add or remove dependencies, configure arguments, and build arguments according to various options a port author wishes to provide. Here is an example of several variants that modify depends_lib and configure arguments for a port.

    variant fastcgi description {Add fastcgi binary} {
        configure.args-append \
                --enable-fastcgi \
                --enable-force-cgi-redirect \
                --enable-memory-limit
    }

    variant gmp description {Add GNU MP functions} {
        depends_lib-append port:gmp
        configure.args-append --with-gmp=${prefix}

    }

    variant sqlite description {Build sqlite support} {
        depends_lib-append \
            port:sqlite3
        configure.args-delete \
            --without-sqlite \
            --without-pdo-sqlite
        configure.args-append \
            --with-sqlite \
            --with-pdo-sqlite=${prefix} \
            --enable-sqlite-utf8
    }

Variant names may contain only the characters A-Z, a-z, and the underscore character _. Therefore, take care to never use hyphens in variant names.

In the example variant declaration below, the configure argument `--without-x` is removed and a number of others are appended.

    variant x11 description {Builds port as an X11 program with Lucid widgets} {
        configure.args-delete   --without-x
        configure.args-append   --with-x-toolkit=lucid \
                                --without-carbon \
                                --with-xpm \
                                --with-jpeg \
                                --with-tiff \
                                --with-gif \
                                --with-png
        depends_lib-append      lib:libX11:XFree86 \
                                lib:libXpm:XFree86 \
                                port:jpeg \
                                port:tiff \
                                port:libungif \
                                port:libpng
    }

### Variant Actions in a Phase

If a variant requires options in addition to those provided by keywords using -append and/or -delete, in other words, any actions that would normally take place within a port installation phase, do not try to do this within the variant declaration. Rather, modify the behavior of any affected phases when the variant is invoked using the variant_isset keyword.

    post-destroot {
        xinstall -m 755 -d ${destroot}${prefix}/etc/
        xinstall ${worksrcpath}/examples/foo.conf \
            ${destroot}${prefix}/etc/

        if {[variant_isset carbon]} {
            delete ${destroot}${prefix}/bin/emacs
            delete ${destroot}${prefix}/bin/emacs-${version}
        }
    }

### Default Variants

Variants are used to specify actions that lie outside the core functions of an application or port, but there may be some cases where you wish to specify these non-core functions by default. For this purpose you may use the keyword default_variants.

    default_variants    +foo +bar

The default_variant keyword may only be used in the global Portfile section.

## Patch Files

Patch files are files created with the Unix command `diff` that are applied using the command `patch` to modify text files to fix bugs or extend functionality.

### Creating Portfile Patches

If you wish to contribute modifications or fixes to a Portfile, you should do so in the form of a patch. Follow the steps below to create Portfile patch files

1. Make a copy of the Portfile you wish to modify; both files must be in the same directory, though it may be any directory.

       %% cp Portfile Portfile.orig

2. Edit the file to make it as you want it to be after it is fetched.

3. Now use the Unix command `diff -u` to create a "unified" diff patch file. Put the name of the port in the patchfile, for example, Portfile-rrdtool.diff.

       %% diff -u Portfile.orig Portfile > Portfile-rrdtool.diff

4. A patch file that is a "unified" diff file is the easiest to interpret by humans and this type should always be used for ports. The Portfile patch below will change the version and checksums when applied.

       --- Portfile.orig        2007-07-25 18:52:12.000000000 -0700
       +++ Portfile    2007-07-25 18:53:35.000000000 -0700
       @@ -2,7 +2,7 @@
        PortSystem          1.0
        name                foo

       -version             1.4.0
       +version             1.3.0
        categories          net
        maintainers         nomaintainer@macports.org
        description         A network monitoring daemon.
       @@ -13,9 +13,9 @@

        homepage            http://rsug.itd.umich.edu/software/${name}

        master_sites        ${homepage}/files/
       -checksums           md5 f0953b21cdb5eb327e40d4b215110b71
       +checksums           md5 01532e67a596bfff6a54aa36face26ae
        extract.suffix      .tgz
        platforms           darwin

Now you may attach the patch file to a MacPorts Trac ticket for the port author to evaluate.

### Creating Source Code Patches

Necessary or useful patches to application source code should generally be sent to the application developer rather than the port author so the modifications may be included in the next version of the application.

Generally speaking, you should create one patch file for each file to be patched. Patchfile filenames should uniquely distinguish the file and generally be of the form `patch-`<directory>-<filename>.diff, as shown in this example: `patch-src-Makefile.in.diff`. Patch files should apply with "patch -p0'' from the working source directory of the port.

You may use patch files that patch multiple files under these conditions:

- You find existing patch files that do so.

- If fixing a particular problem or bug requires changes in multiple files -in those cases the patch filename should reference the problem or bug, for example: `patch-`<destroot_variable_fix>.diff

To create a patch to modify a single file, follow the steps below.

1. Locate the file you wish to patch in its original location within the unpacked source directory and make a duplicate of it.

       %% cd foo-1.34/src
       %% cp Makefile.in Makefile.in.orig

2. Edit the file to make it as you want it to be after it is fetched.

3. Now use the Unix command `diff -u` to create a "unified" diff patch file.

       %% cd foo-1.34
       %% diff -u Makefile.in.orig Makefile.in > patch-Makefile.in.diff

   You must execute the `diff` command in the top-level of the unpacked source code. Otherwise the `patch` command will look for the file to be patched in the wrong path and fail.

4. A patch file that is a "unified" diff file is the easiest to interpret by humans and this type should always be used for ports. See the example below where a patch adds `DESTDIR` support to a `Makefile.in` file.

       --- Makefile.in.orig   2007-06-01 16:30:47.000000000 -0700
       +++ Makefile.in       2007-06-20 10:10:59.000000000 -0700
       @@ -131,23 +131,23 @@
               $(INSTALL_DATA)/gdata $(INSTALL_DATA)/perl

        install-lib:
       -       -mkdir -p $(INSTALL_LIB)
       +       -mkdir -p $(DESTDIR)$(INSTALL_LIB)
               $(PERL) tools/install_lib -s src -l $(INSTALL_LIB) $(LIBS)
       -       cp $(TEXT) $(INSTALL_LIB)/
       +       cp $(TEXT) $(DESTDIR)$(INSTALL_LIB)/

5. Place the patch `patch-Makefile.in.diff` in the directory `${portpath}/files` and use it in a port using the `patchfiles` keyword. `${portpath}` may be in a local Portfile repository for development purposes, or the patch may be committed to the global MacPorts repository for distribution.

       patchfiles          patch-Makefile.in.diff

### Manually Applying Patches

Though MacPorts applies patch files automatically, you may want to know how to apply patch files manually if you want to test patch files you have created or you wish to apply uncommitted Portfile patches.

1. Change to the directory containing the file to be patched. In this example, we'll apply a Portfile patch to the postfix port.

       %% cd $(port dir postfix)

2. Now apply the patch that is on the current user's desktop. The patchfile knows the filename of the file to be patched.

       %% patch -p0 < ~/Desktop/Portfile-postfix.diff

       patching file Portfile

## Local Portfile Repositories

To create and test Portfiles that are not yet committed to subversion, you may create a local Portfile repository as shown. Replace the hypothetical user `julesverne` with your username in the example below.

1. Open the `sources.conf` file in a text editor.

       %% cd ${prefix}/etc/macports/
       %% pico sources.conf

2. Insert a URL pointing to your local repository location before the rsync URL as shown.

       file:///Users/julesverne/ports
       rsync://rsync.macports.org/release/ports

   The file URL should always appear before the rsync URL so that local Portfiles can be tested that are duplicated in the MacPorts tree, because the `port` command will always operate on the first Portfile it encounters.

3. Place the Portfiles you create inside a directory whose name matches the port, which should in turn be placed inside a directory that reflect the port's primary category (the first category entry in the Portfile).

       %% cd /Users/julesverne
       %% mkdir -p ports/games/bestevergame
       %% cd ports/games/bestevergame
       %% touch Portfile

4. After a Portfile is created (see below), use the MacPorts `portindex` command in the local repository's directory to install it into the `Portindex`.

       %% cd /Users/julesverne/ports
       %% portindex

       Creating software index in /Users/julesverne/ports
       Adding port games/bestevergame

       Total number of ports parsed:   1
       Ports successfully parsed:      1
       Ports failed:                   0

Once the local port is added to the `Portindex`, it becomes available for searching or installation as with any other Portfile in the MacPorts tree as shown.

    %% port search bestever

    bestevergame   games/bestevergame 1.1   The Best Ever Game

## Portfile Best Practices

This section contains practical guidelines for creating Portfiles that install smoothly and provide consistency between ports. The following sections are on the TODO list.

### Port Style

Portfiles may be thought of as a table of keys and values in two columns separated by spaces (not tabs), so you should set your editor to use soft tabs, which are tabs emulated by spaces. By default, the top line of all Portfiles should use a modeline that defines soft tabs for the vim and emacs editors as shown.

    # -*- 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

The left column should consist of single words, and will be separated from the more complex right side by spaces in multiples of four. Variable assignments and variant declarations are exceptions, and may be considered a single word on the left side, with a single space between words.

    set libver "8.5"

    variant mysql5 { ... }

Frequently multiple items are necessary in the second column. For example, to set multiple source download locations, multiple "master_sites" must be defined. Unless the second column items are few and short you should place each additional item on a new line and separate lines with a backslash. Indent the lines after the first line to make it clear the items are second column values and also to emphasize the unity of the block.

    destroot.keepdirs    ${destroot}${prefix}/var/run \
                         ${destroot}${prefix}/var/log \
                         ${destroot}${prefix}/var/cache/mrtg

### Don't Overwrite Config Files

TODO:

### Install Docs and Examples

TODO:

### Provide User Messages

TODO:

### Use Variables

TODO: Set variables so changing paths may be done in one place; use them anytime it makes updates simpler: distname ${name}-src-${version}