1. Automating software release packaging from FileMaker

  1. Automating software release packaging from FileMaker
    1. Before the release process was automated
    2. Overview of the solution
      1. Selecting files and folders from FileMaker
      2. The data model
    3. Why the Windows and macOS workflows differ
      1. Windows ZIP release
      2. macOS DMG release
    4. Copying configured release items
    5. Coordinating macOS tools
    6. Notarization and ticket stapling
    7. Uploading the completed release
    8. Logging and error handling
    9. Handing the release to the download manager
    10. Design lessons from the example

This example is valid for ACF-Plugin version 1.7.9.2 and upwards.

Preparing a software release often involves much more than creating an archive. Files must be collected from several locations, signatures must be checked, installers may need to be built, and the final artifacts must be uploaded and published. On macOS, the release may also require notarization and ticket stapling before it can be distributed.

This example shows how a FileMaker solution and the ACF Plugin can coordinate that workflow. FileMaker stores the release configuration and provides the operator interface, while ACF performs file operations, creates archives, runs platform tools, communicates with remote storage, and reports each step back to FileMaker.

The purpose of the example is to demonstrate the architecture and the important integration points. It is not a complete listing of the release application, and signing identities, credentials, server addresses, and other environment-specific values have deliberately been omitted.

1.1. Before the release process was automated

Before this solution was created, preparing a release was a labour-intensive manual task. Xcode performed the necessary build and signing of the application artifacts, but the remaining components still had to be collected and arranged into the final release archive. This included documentation, release notes, examples, supporting files, and other product-specific material. Repeating these steps manually made it easy for releases to differ in structure or for a required file to be overlooked.

The automated workflow starts with the signed artifacts produced by Xcode and takes responsibility for the rest of the release process. The FileMaker solution stores where every additional component is located and where it belongs in the finished package. Documentation and related files can continue to be maintained in their normal locations; the release process collects the current versions when the package is built.

This configuration is reusable between releases of the same product. In the usual case, the operator only needs to select the newly signed application artifacts and update the version number. The solution then recreates the established package structure, performs the required checks and platform-specific processing, and uploads the result. This saves time while also producing more complete and consistent releases.

1.2. Overview of the solution

Screenshot-release-layout

The release solution stores one record for each archive to be produced. Related records describe the files and folders that belong in the archive and where each item should be placed.

1.2.1. Selecting files and folders from FileMaker

The portal shown in the screenshot includes buttons for selecting the source file or directory for each release item. FileMaker does not provide a built-in calculation function for displaying a native file or folder picker and returning the selected path. This is a frequently requested requirement when building utilities and configuration interfaces in FileMaker.

The solution uses two functions from the ACF Bootstrap library, which is included with ACF applications:

ACFU_SelectFileOnly ( string_startPath ; string_Prompt )
ACFU_SelectFolder ( string_prompt )

ACFU_SelectFileOnly opens a native file picker, optionally starting in a specified location, and returns the selected file path. ACFU_SelectFolder opens a native directory picker and returns the selected folder path. If the user cancels the dialog, the existing field value can be retained.

This makes the release configuration easier and safer to maintain. The operator selects an item instead of typing a long path manually, reducing spelling mistakes and invalid paths. The same functions can also be reused anywhere a FileMaker solution needs the user to select a local file or directory.

1.2.2. The data model

The simplified data model is:

Table Purpose
ZipArchive Stores the project name, version, platform, local output path, and remote target folder.
ZipArchiveItems Stores each source path and its destination folder in the release.
ZipConfig Stores environment-specific remote server configuration.
ACF_Downloads Receives the published file name and remote path for the download manager.

The FileMaker script is responsible for orchestration:

  1. Start a build log.
  2. Call the appropriate ACF function for the target platform.
  3. Add the ACF console output to the log after each operation.
  4. Stop immediately and show an error if an operation fails.
  5. Store the completed release name and remote path for publication.

The ACF package is responsible for the lower-level work:

1.3. Why the Windows and macOS workflows differ

The same FileMaker solution builds releases for both platforms, but the two workflows are deliberately different.

1.3.1. Windows ZIP release

The Windows release is written directly into a ZIP archive. ACF opens the ZIP and copies each registered source item to an explicit path inside it. When all items have been copied, ACF closes the archive and uploads the finished ZIP file.

The release is currently initiated on a Mac, but it does not use the macOS zip command on a folder. Because ACF adds only the registered files and folders, macOS metadata such as .DS_Store and __MACOSX is not included in the Windows archive.

The complete high-level operation is exposed as one ACF function:

function PackZipArchive (
    string PrimaryKey,
    string outputZipPath,
    string remoteArchiveFolder
)

    string res = CopyArchiveItemsToZip(PrimaryKey, outputZipPath);

    if (res != "OK") then
        throw res;
    end if

    return UploadReleaseFile(outputZipPath, remoteArchiveFolder);

end

The corresponding FileMaker script can therefore remain small:

Set Variable [ $result ;
    ACF_run ( "PackZipArchive" ;
        ZipArchive::PrimaryKey ;
        ZipArchive::ZipFileName ;
        ZipArchive::FullTargetPath
    )
]
Set Field [ ZipArchive::Console ; ACF_GetConsoleOutput ]

1.3.2. macOS DMG release

The macOS workflow needs an inspectable staging folder because artifacts must be verified, installers may need to be constructed, and several items may require notarization before the final disk image is made.

The process is:

  1. Recreate the staging folder.
  2. Copy all registered release items into it.
  3. Identify applications, plugins, packages, disk images, and command-line tools that require additional processing.
  4. Verify the existing code signatures.
  5. Build a signed installer package where required.
  6. Submit the staged artifacts to Apple's notary service.
  7. Staple and validate the accepted notarization tickets.
  8. Create the final DMG from the staging folder.
  9. Notarize, staple, and validate the DMG.
  10. Upload the completed DMG to the remote release archive.

Separating staging from DMG creation also gives the operator an opportunity to inspect the prepared release before the final archive is produced.

1.4. Copying configured release items

The source files are not hard-coded in the ACF package. ACF reads the release definition from the FileMaker tables with ExecuteSQL. NOTE the INTO part at the bottom that is an ACF extention to FileMaker SQL to store the SQL result in arrays instead of producing a CSV style dump.

array string FilePaths;
array string TargetFolders;
array int    IsDirectory;
array int    IsFileExists;
    
string sql =
"SELECT
        FileLocalFilePath,
        TargetFolder,
        isDirectory,
        isFileExists
    FROM
        ZipArchiveItems
    WHERE
        ArchiveFK = :PrimaryKey
    ORDER BY
        TargetFolder,
        FileLocalFilePath

INTO :FilePaths, :TargetFolders, :IsDirectory, :IsFileExists
";

string res = ExecuteSQL(sql);

Each source path is combined with its configured target folder. For a ZIP release, the target is a path inside the open archive:

string zip = open_zip(outputZipPath);

for (i = 1, sizeof(FilePaths))
    string targetPath = MakeZipTargetPath(
        zip,
        TargetFolders[i],
        FilePaths[i]
    );

    res = copy_file(FilePaths[i], targetPath);
end for

close_zip(zip);

For a macOS release, the same configuration is used to copy the items into a normal staging folder instead. This keeps release contents under FileMaker control while allowing platform-specific processing afterward.

Before copying, the implementation verifies that the source exists. A missing item causes an exception rather than producing a partial release.

1.5. Coordinating macOS tools

ACF can use SystemCommand to coordinate command-line tools and capture their output. The actual signing identities and notary profiles should be stored in protected configuration rather than embedded in code intended for distribution.

Typical tools used in this workflow include:

Tool Role
codesign Verifies signatures on applications, plugins, disk images, and executable code.
pkgbuild Creates and signs a macOS installer package.
pkgutil Verifies the installer package signature and timestamp.
xcrun notarytool Submits an artifact to Apple's notary service and waits for the result.
xcrun stapler Attaches and validates the accepted notarization ticket.
DropDMG Creates the final DMG using a predefined visual configuration.

A command is constructed with quoted paths, executed in the appropriate working directory, and then checked for both the expected output and the expected resulting file.

command = "codesign --verify --strict --verbose=2 " + ShellQuote(Path);
print "\nVerifying signature:\n" + command + "\n";

string res = SystemCommand(command, GetDirectoriesFromPath(Path));
print res + "\n";

if (pos(res, "valid on disk") < 0) then
    throw "Signature verification failed:\n" + res;
end if

The same principle is used after pkgbuild and DropDMG: successful command execution alone is not considered sufficient. The workflow also verifies that the expected .pkg or .dmg file was created.

1.6. Notarization and ticket stapling

Applications and FileMaker plugin bundles are placed in a temporary ZIP before submission. Installer packages and disk images can be submitted directly.

Before submitting an artifact, the workflow asks stapler whether it already has a valid ticket. This makes the operation safer to repeat and avoids submitting an already notarized artifact again.

The simplified sequence is:

xcrun stapler validate <artifact>
xcrun notarytool submit <artifact> --keychain-profile <profile> --wait
xcrun stapler staple <artifact>
xcrun stapler validate <artifact>

The final validation is important. It confirms that the accepted ticket was attached to the distributed artifact, not only that Apple accepted the submission.

1.7. Uploading the completed release

ACF uses the same path-based file API for local files, ZIP archives, and FTP connections. The upload function opens the configured remote server, creates the full destination path, copies the artifact, and closes the connection.

function UploadReleaseFile (
    string localPath,
    string remoteArchiveFolder
)

    if (! file_exists(localPath)) then
        throw "Source file does not exist: " + localPath;
    end if

    string ftp = ConnectRemoteServer();
    remoteArchiveFolder = EnsureTrailingSlash(remoteArchiveFolder);

    string remoteTarget = ftp
        + remoteArchiveFolder
        + GetFilenameFromPath(localPath);

    string res = copy_file(localPath, remoteTarget);
    close_ftp(ftp);

    if (res != "OK") then
        throw "FTP upload failed: " + res;
    end if

    return "OK";

end

In production, the server name and credentials come from configuration fields. They should not be printed in the build log or included in source code examples.

1.8. Logging and error handling

Every ACF function prints useful progress information to the ACF console. The FileMaker script retrieves that text with ACF_GetConsoleOutput and appends it to a build log after each call.

A successful macOS log shows a progression similar to this:

Copying release items to the staging folder
Verifying command-line utility signature
Building installer package
Verifying installer package signature
Submitting installer for notarization
Notarization status: Accepted
Stapling and validating installer ticket
Creating DMG
Submitting DMG for notarization
Notarization status: Accepted
Stapling and validating DMG ticket
Uploading completed DMG
Result: OK

Each operation returns "OK" on success or throws an exception with a detailed message. The FileMaker script checks the result before continuing. A failed signature check, missing source file, rejected notarization, missing output artifact, or failed upload therefore stops the release before it can be published.

1.9. Handing the release to the download manager

After a successful upload, FileMaker stores the release name and the remote file path in the related download record. The download manager can then call the website's WordPress API to update the public download information.

Keeping this as a separate final step has two advantages:

The workflow can be made fully automatic, or the final API call can require operator confirmation depending on the release policy.

1.10. Design lessons from the example

This release system demonstrates several patterns that are useful beyond software packaging:

The result is a repeatable release process controlled from FileMaker, while ACF provides access to the operating system and remote services needed to complete the work.