How the Hallelujah Input Method Is Packaged as a .pkg Installer Using build-package.bash

The build-package.bash script automates a four-stage pipeline—compiling the Xcode project, preparing a payload root, invoking pkgbuild with specific metadata flags, and executing a postinstall script—to generate a versioned macOS installer that automatically registers the input method in /Library/Input Methods.

The Hallelujah input method, hosted at dongyuwei/hallelujahim, requires installation into macOS system directories to function as a third-party keyboard input source. Rather than manual distribution of the .app bundle, the repository uses build-package.bash to orchestrate the entire build-to-package workflow. This script coordinates Xcode compilation, payload assembly, and installer generation while embedding a post-installation hook that handles system registration without user intervention.

The Four-Stage Packaging Pipeline

The packaging process defined in package/build-package.bash follows a strict sequence to ensure the resulting .pkg meets macOS requirements for input method installation.

Stage 1: Compiling the Xcode Project

The script initiates the process by invoking build.sh from the repository root. This companion script executes xcodebuild using the hallelujah scheme in Release configuration, producing hallelujah.app and placing it in /tmp/hallelujah/build/release/.

pushd ${PROJECT_ROOT}
sh build.sh
popd

According to the source code in build.sh, this step cleans previous build artifacts and compiles the Objective-C++ source files (such as src/InputController.mm and src/ConversionEngine.mm) into the final application bundle.

Stage 2: Preparing the Payload Root

Before invoking pkgbuild, the script establishes a temporary directory structure that serves as the installer payload. It removes any existing root directory to prevent stale files, then creates a fresh hierarchy and copies the compiled .app bundle into position.

rm -rf /tmp/hallelujah/build/release/root/
mkdir -p /tmp/hallelujah/build/release/root
cp -R /tmp/hallelujah/build/release/hallelujah.app \
      /tmp/hallelujah/build/release/root/

This root directory (/tmp/hallelujah/build/release/root) becomes the --root argument for pkgbuild, determining what files the installer places on the target system.

Stage 3: Generating the .pkg with pkgbuild

The core packaging operation occurs via pkgbuild with six critical flags defined in lines 19–26 of build-package.bash:

  • --info: Points to package/PackageInfo, a minimal XML manifest specifying package metadata
  • --root: The temporary root directory containing hallelujah.app
  • --identifier: Reverse-DNS string github.dongyuwei.inputmethod.hallelujahInputMethod for unique system identification
  • --version: Timestamp generated via date "+%Y%m%d%H%M%S" to ensure unique versioning
  • --install-location: Hardcoded to /Library/Input Methods, the mandatory system directory for third-party input methods
  • --scripts: References package/scripts/ containing the postinstall hook
pkgbuild \
    --info "${PROJECT_ROOT}/package/PackageInfo" \
    --root "/tmp/hallelujah/build/release/root" \
    --identifier "github.dongyuwei.inputmethod.hallelujahInputMethod" \
    --version ${Version} \
    --install-location "/Library/Input Methods" \
    --scripts "${PROJECT_ROOT}/package/scripts" \
    /tmp/hallelujah-${Version}.pkg

The command outputs a versioned installer file at /tmp/hallelujah-${Version}.pkg, where ${Version} expands to the current timestamp.

Stage 4: Automated Registration with postinstall

The postinstall script (located at package/scripts/postinstall) executes automatically after file installation completes. This bash script performs three critical registration tasks:

  1. Terminates existing instances: Kills any running hallelujah process and removes previous user-level copies from ~/Library/Input Methods
  2. Registers the new input method: Launches the freshly installed app with the --install flag, triggering macOS system registration
  3. Refreshes system caches: Restarts cfprefsd to ensure the input method appears immediately in System Preferences → Keyboard → Input Sources

Building and Installing Locally

To generate the installer from source, execute the packaging script from the package directory:

cd package
./build-package.bash

The script produces a timestamped package in /tmp/ (e.g., /tmp/hallelujah-20241015103045.pkg). Install it using the macOS installer command:

sudo installer -pkg /tmp/hallelujah-20241015103045.pkg -target /

During installation, the bundled postinstall script automatically handles system registration. Verify successful installation by checking the system's enabled input sources:

defaults read /Library/Preferences/com.apple.HIToolbox.plist AppleEnabledInputSources

Look for the identifier github.dongyuwei.inputmethod.hallelujahInputMethod in the output array.

Key Files in the Packaging Architecture

  • package/build-package.bash: Orchestrates the entire workflow from compilation to package generation
  • build.sh: Executes the Xcode build process for the Release configuration
  • package/PackageInfo: XML manifest providing metadata required by pkgbuild
  • package/scripts/postinstall: Post-installation script that registers the input method and refreshes system caches
  • src/: Contains Objective-C++ implementation files (e.g., InputController.mm, ConversionEngine.mm) compiled into the final app bundle

Summary

  • Compilation: build-package.bash triggers build.sh to create a Release build of hallelujah.app via Xcode
  • Payload preparation: The script copies the compiled app into a temporary root directory at /tmp/hallelujah/build/release/root/
  • Package generation: pkgbuild assembles the .pkg using --identifier, --version (timestamp), and --install-location targeting /Library/Input Methods
  • System integration: The postinstall script automatically registers the input method with macOS and refreshes preference caches without manual user steps

Frequently Asked Questions

What is the purpose of the postinstall script in the Hallelujah package?

The postinstall script ensures the input method becomes immediately available after installation. It kills any running instance of the app, removes outdated user-level copies from ~/Library/Input Methods, launches the new version with the --install flag to register with macOS, and restarts the cfprefsd daemon to refresh the input source cache.

Why does build-package.bash use a timestamp for the package version?

The script generates the version using date "+%Y%m%d%H%M%S" to create a unique, monotonically increasing version string for every build. This ensures that each .pkg file has a distinct identifier, preventing conflicts with previous installations and simplifying distribution tracking.

Where does the installer place the Hallelujah input method?

According to the --install-location flag in build-package.bash, the installer places hallelujah.app directly into /Library/Input Methods, the standard macOS system directory for third-party input methods. This system-wide location makes the input method available to all user accounts on the machine.

Can I modify the package identifier or install location?

Yes, by editing package/build-package.bash before running the script. Change the --identifier flag to your own reverse-DNS string, or modify --install-location to target a different directory (though /Library/Input Methods is required for system-wide input method functionality). Re-run the script to generate a customized installer.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →