6. Working with Files and Folders

The installation resources (files and directories) are grouped into <folder> elements.

<project>
    ...
    <componentList>
       <component>
          <name>myComponent</name>
          ...
          <folderList>
              <folder>
                 <name>documents</name>
                 <destination>${installdir}/docs</destination>
                 <platforms>linux windows</platforms>
                 <distributionFileList>
                     <distributionDirectory origin="/home/johndoe/docs"/>
                     <distributionFile origin="/home/johndoe/README"/>
                 </distributionFileList>
                 <shortcutList>
                     ...
                     <shortcut/>
                     ...
                 </shortcutList>
                 <actionList>
                     <addTextToFile file="${installdir}/docs/README" text="some text"/>
                 </actionList>
              </folder>
          </folderList>
       </component>
    </componentList>
</project>

The most important tags in a folder element are:

<folder>
   ...
   <distributionFileList>
       <distributionFile>
          <origin>${build_project_directory}/license.txt</origin>
       </distributionFile>
       <distributionDirectory allowWildcards="1">
          <origin>${build_project_directory}/readmeFiles/*.txt</origin>
          <includeFiles>*/README-*.txt</includeFiles>
          <excludeFiles>*/README-template.txt</excludeFiles>
       </distributionDirectory>
   </distributionFileList>
</folder>
Supported Platforms

Platform Identifier

Platform Description

all

All Platforms

linux

Linux

linux-x64

Linux x86 64 bits

linux-ia64

Linux IA64

windows

Windows

osx

Mac OS X

solaris-sparc

Solaris Sparc

solaris-intel

Solaris Intel

linux-ppc

Linux PPC

linux-s390

Linux s390

linux-s390x

Linux s390x

freebsd

FreeBSD 5

freebsd4

FreeBSD 4

freebsd6

FreeBSD 6

freebsd6-x64

FreeBSD 6 64 bits

freebsd7

FreeBSD 7/8

freebsd7-x64

FreeBSD 7/8 64 bits

openbsd3

OpenBSD x86

hpux

HP-UX

aix

AIX, OS/400

irix-n32

IRIX

6.1. Conditionally Packing a Folder

Although the <platforms> tag provides a built-in mechanism to avoid packing folders for different platforms, there are scenarios in which you need a more complex condition than just the target platform. This can be achieved using the <shouldPackRuleList>:

  <folder>
    <name>documents</name>
    <destination>${installdir}/docs</destination>
    <platforms>linux</platforms>
    <distributionFileList>
       <distributionDirectory origin="/home/johndoe/docs"/>
       <distributionFile origin="/home/johndoe/README"/>
    </distributionFileList>
    <shouldPackRuleEvaluationLogic>and</shouldPackRuleEvaluationLogic>
    <shouldPackRuleList>
       <fileExists path="/home/johndoe/docs"/>
       <fileExists path="/home/johndoe/README"/>
    </shouldPackRuleList>
  </folder>

The above folder will only be packed if the target platform is linux and the files exist. The evaluation logic of the rules can be modified through the <shouldPackRuleEvaluationLogic> property.

This is also helpful when you are developing the installer and do not need all of the files that will be included in the final version (that can greatly increase the build time) to be packed. For example, you could create an environment variable (DEMO_BUILD) to indicate to the builder you do not need an official build:

  <folder>
    <name>optionalFiles</name>
    <destination>${installdir}</destination>
    <platforms>linux</platforms>
    <distributionFileList>
       <distributionDirectory origin="videos"/>
       <distributionDirectory origin="images"/>
       <distributionDirectory origin="documentation"/>
    </distributionFileList>
    <shouldPackRuleList>
       <!-- Check if the variable is defined -->
       <compareText text="${env(DEMO_BUILD)}" logic="equals" value=""/>
    </shouldPackRuleList>
  </folder>

You can find a more complex example in the "Custom Build Targets" section.

[Note]Do not confuse <shouldPackRuleList> with <ruleList>

Rules inside a <ruleList> are evaluated at runtime while rules within a <shouldPackRuleList> are evaluated at build time.

6.2. Conditionally Unpacking a Folder

By default, folders will be unpacked if they were packed (as described in the previous section) and if the <component> in which they are bundled was selected. However, you can add additional logic to discern if it should be unpacked by attaching rules to its <ruleList>:

  <folder>
    <name>documents</name>
    <destination>${installdir}/docs</destination>
    <platforms>windows</platforms>
    <distributionFileList>
       <distributionDirectory origin="/home/johndoe/docs"/>
       <distributionFile origin="/home/johndoe/README"/>
    </distributionFileList>
    <ruleEvaluationLogic>and</ruleEvaluationLogic>
    <ruleList>
       <platformTest type="windows-xp"/>
    </ruleList>
  </folder>

The above folder will be packed for any Windows target but will only be unpacked if the platform in which the installer is executing is Windows XP.

6.3. Filters

Basic Filters

InstallBuilder implements a basic filtering mechanism for folders. This functionality will allow you to pack the contents of a folder instead of including the folder itself and to apply filters to include or exclude specific files. For example, if you have a folder named "documentation" and you want to pack a subset of its contents and unpack it to ${installdir}/myDocs, you just need to use the snippet below:

<folder>
   <description>Documentation Files</description>
   <destination>${installdir}/myDocs</destination>
   <name>documentation</name>
   <platforms>all</platforms>
   <distributionFileList>
       <distributionDirectory allowWildcards="1">
          <origin>/some/path/to/documentation/*</origin>
          <includeFiles>*/project-1-*.txt</includeFiles>
          <excludeFiles>*/project-1-secret.txt</excludeFiles>
       </distributionDirectory>
   </distributionFileList>
</folder>

In the above example, all of the .txt files related to "project-1" will be packaged, with the exception of "project-1-secret.txt"

The following are the filtering-related tags that you can use:

  • <allowWildcards> : This tag determines the behavior of the filtering mechanism. If it is set to "0" (the default), there will not be any global pattern interpretation and patterns will be taken as literal strings. For example, if you define <origin>/some/path/to/documentation/*</origin>, the installer will look for a folder named "*" inside the /some/path/to/documentation/ directory. In addition, the includeFiles and excludeFiles tags will be ignored.

However, if allowWildcards is set to "1", the <origin> will be expanded and the installer will generate a list of matching files.

  • <includeFiles> : This tag allows you to select files over the matches produced by the <origin> tag when wildcards are enabled. The default value of the <includeFiles> tag is "*" so all the files matched by the <origin> tag will be packed.
  • <excludeFiles> : Once the list of files has been filtered with the <includeFiles> pattern, the <excludeFiles> filter will be applied over the result. The default value of this tag is empty so no filter is applied. This tag is ignored if wildcards are disabled. Only files directly returned by the evaluation of the <origin> pattern can be excluded, the builder won’t try to recursively exclude files at deeper hierarchy levels in the packed directories.

The pattern interpretation in the <origin>, <includeFiles> and <excludeFiles> does not follow the same rules. The <origin> pattern is expanded over the existing files, like a "dir c:\Program Files\*txt" on Windows or "ls ~/*txt" on Unix. Forward and backwards slashes are normalized according to the platform. However, the patterns in the <includeFiles> and <excludeFiles> tags follow a stricter format:

  • Forward slashes are used as path separators, which let us escape special characters using backslashes. For example, to match .txt files, you must use */*.txt files in both Windows and Unix systems.
  • The pattern is applied over the full path of the files and a string comparison is performed: <includeFiles>my.txt<includeFiles> will not work because it will compare "/some/path/to/my.txt" with "my.txt". */my.txt has to be used instead.
  • Multiple patterns can be included and must be separated by ; or \n characters:

   <distributionDirectory allowWildcards="1">
       <origin>/some/path/*</origin>
       <includeFiles>*/*.txt;*/*.jpg;*/*.bmp</includeFiles>
       <excludeFiles>*/*secret*.txt;*/myImage.bmp</excludeFiles>
    </distributionDirectory>

The special characters used in all of the patterns are listed below:

  • ? : Matches any single character.
  • * : Matches any sequence of zero or more characters.
  • [chars] : Matches any single character in chars. If chars contains a sequence of the form a-b then any character between a and b (inclusive) will match.
  • {a,b,...} : Matches any of the strings a, b, etc.
[Note]Basic filters do not operate recursively

The basic filters mechanism is not intended to recursively skip packing files in the directory provided in the <origin> tag. Just the results returned by the evaluation of the <origin> pattern can be filtered. For example, if you are packing a directory images and want to exclude the file demo.png, the below won’t work:

<folder>
   ...
   <destination>${installdir}</destination>
   ...
   <distributionFileList>
       <distributionDirectory allowWildcards="1">
          <origin>images</origin>
          <excludeFiles>*/demo.png</excludeFiles>
       </distributionDirectory>
   </distributionFileList>
</folder>

The reason is that the <origin> pattern will only return images as matching file, which does not match the exclusion pattern */demo.png.

If demo.png is packed directly under images you could use:

<folder>
   ...
   <destination>${installdir}/images</destination>
   ...
   <distributionFileList>
       <distributionDirectory allowWildcards="1">
          <origin>images/*</origin>
          <excludeFiles>*/demo.png</excludeFiles>
       </distributionDirectory>
   </distributionFileList>
</folder>

In this case, evaluating <origin> will return all the contained images (sample-01.png, left-image.jpg, details.gif, demo.png, …), and the exclusion pattern will be able to match demo.png.

If you want to filter certain files at any level of the hierarchy in the packed directory, you should use the Advanced Filters instead, which operate recursively.

Advanced Filters

In addition to the basic filters, which allow providing a pattern to the <origin> tag while excluding some of the results returned, InstallBuilder also includes a more complex filtering mechanism that allows excluding files located at any level in the folder hierarchy of the <distributionDirectory>.

Note that you have to define filters that will decide if the file will be packed or not. Currently, just filters based on the file path are allowed: <fileNameFilter>. These are the basic tags in the <fileNameFilter>:

  • <pattern>: Pattern to match against the path of the file
  • <patternType>: Whether to use glob or regular expressions matching
  • <logic>: Whether or not the pattern must match to pass the filter.

These filters are grouped into the <onPackingFilterList>. If the file in consideration matches the expressed conditions in this list, it will be packed, if not, it will be skipped.

For example, if you want to pack a folder dist-files:

dist-files/
|-- bin
|   `-- productA
|       `-- data
|           |-- info.ini
|           `-- info.ini~
|-- docs
|   `-- reference
|       |-- README.tct~
|       |-- reference.html
|       |-- reference.html~
|       |-- reference.pdf
|       `-- reference.pdf~
`-- libraries
    |-- lib1.so
    |-- .#lib2.so
    `-- lib2.so

To exclude all of the temporary files (files ending in ~), you could use:

 <folder>
   <description>Program Files</description>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <platforms>all</platforms>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <onPackingFilterList>
          <fileNameFilter pattern="*~" logic="does_not_match" patternType="glob"/>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

After the installation, you will find the dist-files directory clean of temporary files:

sample-1.0
|-- dist-files
|   |-- bin
|   |   `-- productA
|   |       `-- data
|   |           `-- info.ini
|   |-- docs
|   |   `-- reference
|   |       |-- reference.html
|   |       `-- reference.pdf
|   `-- libraries
|       |-- lib1.so
|       |-- .#lib2.so
|       `-- lib2.so
|-- uninstall
`-- Uninstall Sample Project.desktop

If you also want to exclude files starting with .#, you just need to add another filter to the <onPackingFilterList>:

 <folder>
   <description>Program Files</description>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <platforms>all</platforms>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <filterEvaluationLogic>and</filterEvaluationLogic>
       <onPackingFilterList>
          <fileNameFilter pattern="*~" logic="does_not_match" patternType="glob"/>
          <fileNameFilter pattern="*/.#*" logic="does_not_match" patternType="glob"/>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

The default <filterEvaluationLogic> is and but you can also set it to or. A file will be packed if its full path in the build machine matches all the filters when using and logic or when matching any of them when using or.

For example, if you want to pack just .png and .jpg files, you could rewrite the code with or logic:

 <folder>
   <description>Program Files</description>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <platforms>all</platforms>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <filterEvaluationLogic>or</filterEvaluationLogic>
       <onPackingFilterList>
          <fileNameFilter pattern="*.png" logic="matches" patternType="glob"/>
          <fileNameFilter pattern="*.jpg" logic="matches" patternType="glob"/>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

The filters can also include more complex patterns if instead of using the glob <patternType> you set it to regexp. For example, you can exclude .txt files that start with a number and contain temp in their paths and .png files that start with three capital letters using a single filter:

 <folder>
   <description>Program Files</description>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <platforms>all</platforms>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <onPackingFilterList>
          <fileNameFilter pattern="/([A-Z]{3}[^/]*\.png|[0-9]+[^/]*temp[^/]*\.txt)$" logic="does_not_match" patternType="regexp"/>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

Both regexp and glob pattern types allow providing a semicolon-separated list of sub patterns. If any of the sub patterns in the list evaluates to true, the full pattern will also evaluate to true. For example, to pack all of the .png and .jpg files in a directory, you could use any of the below snippets:

 <folder>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <onPackingFilterList>
          <fileNameFilter pattern="*.png;*.jpg" logic="matches" patternType="glob"/>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

 <folder>
   <description>Program Files</description>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <platforms>all</platforms>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <filterEvaluationLogic>or</filterEvaluationLogic>
       <onPackingFilterList>
          <fileNameFilter pattern="*.jpg" logic="matches" patternType="glob"/>
          <fileNameFilter pattern="*.png" logic="matches" patternType="glob"/>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

 <folder>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <filterEvaluationLogic>and</filterEvaluationLogic>
       <onPackingFilterList>
          <fileNameFilter pattern=".*\.(png|jpg)$" logic="matches" patternType="regexp"/>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

Even more complex conditions can be expressed using a <filterGroup>. This special type of filter allows grouping a set of filters with a configurable <filterEvaluationLogic>. For example, you can exclude all of the .txt files except for those in the readme folder and exclude all of the .png and .jpg files that are not under the images folder:

 <folder>
   <description>Program Files</description>
   <destination>${installdir}</destination>
   <name>programfiles</name>
   <platforms>all</platforms>
   <distributionFileList>
     <distributionDirectory>
       <origin>dist-files</origin>
       <filterEvaluationLogic>and</filterEvaluationLogic>
       <onPackingFilterList>
          <!-- Include files that do not end in .txt or if they are located in the readme dir -->
          <filterGroup>
             <filterEvaluationLogic>or</filterEvaluationLogic>
             <onPackingFilterList>
               <fileNameFilter pattern="*.txt" logic="does_not_match" patternType="glob"/>
               <fileNameFilter pattern="*/readme/*" logic="matches" patternType="glob"/>
             </onPackingFilterList>
          </filterGroup>
          <!-- Include files that do not end in .png or .jpg or if they are located in the images dir -->
          <filterGroup>
             <filterEvaluationLogic>or</filterEvaluationLogic>
             <onPackingFilterList>
               <fileNameFilter pattern=".*\.png;.*\.jpg" logic="does_not_match" patternType="regexp"/>
               <fileNameFilter pattern="*/images/*" logic="matches" patternType="glob"/>
             </onPackingFilterList>
          </filterGroup>
       </onPackingFilterList>
     </distributionDirectory>
   </distributionFileList>
 </folder>

Filter groups can also be nested as needed.

Take into account that all of the files in the <distributionDirectory> will be matched against the filter, so using a lot of very complex files could slightly increase the build time (the performance at runtime will not be affected at all).

[Note]The pattern is applied to the full path of the file

Because of the pattern is applied to the full path of the file to be packed, when trying to match files ending in an specific suffix, you should always prefix it with * (when using glob <patternType>) or .* (when using regexp <patternType>).

For example, specifying:

<fileNameFilter pattern="images/*.png" logic="matches" patternType="glob"/>

Wont work because it will be matched against the full path, for example: /home/bitrock/demo/files/images/foo.png

To make it independent to the location, it should be rewritten to:

<fileNameFilter pattern="*/images/*.png" logic="matches" patternType="glob"/>

Or

<fileNameFilter pattern="${build_project_directory}/files/images/*.png" logic="matches" patternType="glob"/>

6.4. Unix Permissions

InstallBuilder preserves the permissions of bundled files. It also includes two convenient tags: <defaultUnixGroup> and <defaultUnixOwner>. If a value is specified for those tags, the owner and group of the unpacked files will be modified at runtime:

<project>
   ...
   <defaultUnixGroup>wheel</defaultUnixGroup>
   <defaultUnixOwner>root</defaultUnixOwner>
   ...
</project>

Please take into account that the specified group and owner must exist in the target machine. If not, you can always create them before the installation phase:

<project>
   ...
   <defaultUnixGroup>coolUsers</defaultUnixGroup>
   <defaultUnixOwner>john</defaultUnixOwner>
   ...
   <readyToInstallActionList>
      <addUser username="john"/>
      <addGroup groupname="coolUsers"/>
   </readyToInstallActionList>
   ...
</project>

Despite the permissions being preserved, in some situations they must be fixed. The most convenient way of fixing them is to use the <actionList> of the folder containing the files and set the appropriate rights:

<folder>
   <description>Binary Files</description>
   <destination>${installdir}</destination>
   <name>executables</name>
   <platforms>all</platforms>
   <distributionFileList>
       <distributionDirectory>
          <origin>/some/path/executables</origin>
       </distributionDirectory>
   </distributionFileList>
   <actionList>
       <changePermissions permissions="0755" files="${installdir}/executables/*"/>
   </actionList>
</folder>

A common scenario in which permissions must be fixed is when you are building a Linux installer on Windows. In that case, you could either manually fix the permissions as explained in the latest snippet or define the default permissions:

<project>
   ...
   <defaultUnixFilePermissions>644</defaultUnixFilePermissions>
   <defaultUnixDirectoryPermissions>755</defaultUnixDirectoryPermissions>
   ...
</project>

Please take into account that, contrary to the owner and group tags, the default Unix permissions are only applied when building on Windows. This way, if the installer is created on Unix, the configured values will be ignored.

[Note]Files packed on Windows lose their executable permissions

Windows does not understand Unix permissions, so Unix installers created on Windows will be unpacked without executable permissions and must be manually fixed. It is recommended that you use Unix machines to build the installers as they do not present any limitation.

6.5. Symbolic Links

When a symbolic link is specified as a <distributionFile>, InstallBuilder does not follow the link to pack its target. Depending on the build type, it will either pack the link (deb, rpm) or it will be registered and recreated at runtime.

6.6. Unpacking Before Installation Time

It is common to have a separate tool or program that must be bundled with and run from the installer but before the file copying phase of the installation process has completed. A common example would be a license validation program. Typically, all files bundled within an installer are unpacked and then any tools are run. In the case of a license validation tool, that is less than ideal because the user may end up waiting for the files to be unpacked only to find that the license is not valid. The user would then have to wait for the installation to be rolled back.

InstallBuilder provides you with actions to deal with these situations. The most important are <unpackFile> and <unpackDirectory>:

<unpackDirectory>
   <destination>${installdir}</destination>
   <component>tools</component>
   <folder>license</folder>
   <origin>management</origin>
</unpackDirectory>

<unpackFile>
   <destination>${system_temp_directory}</destination>
   <component>tools</component>
   <folder>license</folder>
   <origin>management/validator.exe</origin>
</unpackFile>

The <unpackDirectory> action is intended to unpack a directory and its contents while the <unpackFile> action operates over files. Trying to unpack the wrong type in those actions will generate an error at runtime.

The configuration options for these actions specify the folder and component bundling the files, the path relative to the packed element, and the destination directory to unpack them.

The structure in Figure 33 (also represented in XML code) should help you understand how to reference internal files:

Figure 34. Internal Files Structure

Internal Files Structure

<project>
  ...
  <componentList>
     <component>
       <name>componentA</name>
       <description>Component A</description>
       <folderList>
         <folder>
           <name>folder1</name>
           <description>Folder 1</description>
           <distributionFileList>
              <distributionDirectory>
                <!-- someDirectory contains: readme.txt,
                logo.jpg and directory1 -->
                <origin>/path/to/someDirectory</origin>
              </distributionDirectory>
              <distributionFile>
                <origin>/path/to/someFile</origin>
              </distributionFile>
              <distributionFile>
                <origin>/path/to/someOtherFile</origin>
              </distributionFile>
           </distributionFileList>
         </folder>
       </folderList>
     </component>
     <component>
       <name>componentB</name>
       <description>Component B</description>
       <folderList>
         <folder>
           <name>folder2</name>
           <description>Folder 2</description>
           <distributionFileList>
              <distributionDirectory>
                <!-- someDirectory contains: movie.avi and
                installer.exe -->
                <origin>/path/to/someOtherDirectory</origin>
              </distributionDirectory>
           </distributionFileList>
         </folder>
       </folderList>
     </component>
  </componentList>
  ...
</project>

To reference logo.jpg:

<unpackFile>
  <component>componentA</component>
  <folder>folder1</folder>
  <origin>someDirectory/logo.jpg</origin>
  <destination>${installdir}</destination>
</unpackFile>

You could also unpack directory1:

<unpackDirectory>
  <component>componentA</component>
  <folder>folder1</folder>
  <origin>someDirectory/directory1</origin>
  <destination>${installdir}</destination>
</unpackDirectory>

Please note the <origin> is always provided as the relative path to the file or directory to unpack, relative to the <folder> containing it. It does not matter where the file was originally located in the building machine, just the path inside the installer:

<component name="componentA">
   <folderList>
     <folder>
       <name>folder1</name>
       <distributionFileList>
         <distributionDirectory>
            <origin>/some/long/path/in/the/building/machine/someDirectory</origin>
         </distributionDirectory>
       </distributionFileList>
     </folder>
   </folderList>
</component>

The example below details a real scenario, in which the installer unpacks a license validator and checks the provided key in the <validationActionList> of the page:

<project>
  ...
  <componentList>
    <component>
    <name>tools</name>
       <folderList>
          <folder>
             <name>license</name>
             <destination>${installdir}</destination>
             <distributionFileList>
                <!-- The management directory contains a lot of
                tools, one of the our validator.exe -->
                <distributionDirectory origin="/path/to/dir/to/management"/>
             </distributionFileList>
          </folder>
       </folderList>
    </component>
  </componentList>
  <parameterList>
    <stringParameter>
       <name>licenseCheck</name>
       <description>Introduce your license key</description>
       <value></value>
       <validationActionList>
         <unpackFile>
            <component>tools</component>
            <folder>license</folder>
            <!-- Relative path from the packed folder 'management' -->
            <origin>management/validator.exe</origin>
            <destination>${system_temp_directory}</destination>
         </unpackFile>
         <runProgram>
           <program>${system_temp_directory}/validator.exe</program>
           <programArguments>${project.parameter(licenseCheck).value}</programArguments>
         </runProgram>
         <throwError text="Wrong license key, please enter a valid one">
           <ruleList>
             <compareText text="${program_stdout}" logic="equals" value="1"/>
           </ruleList>
         </throwError>
       </validationActionList>
    </stringParameter>
  </parameterList>
    ...
</project>