Skip to main content

Mage Cross-platform Build Instructions

In Go projects, “build” is often understood as compiling a set of binaries. For OpenIM, however, that is only the first step. OpenIM is made of multiple services, auxiliary tools, deployment environments, operating systems, and CPU architectures. Compiling binaries does not automatically mean the system is ready to ship.

The real questions are broader: where are the targets? Which platforms should they be built for? How many instances should run? What should start first, tools or services? How do we know the processes are really alive? How can the generated artifacts be moved to another machine and still remain runnable?

The core value of gomake is that it pulls these questions into one delivery workflow: Mage is the entry point, directory conventions define the boundary, and runtime configuration plus process checks close the loop. It is not just a wrapper around go build; it puts build, start, stop, check, and export into one shared model of paths, platforms, and processes.

01. Why go build Is Not Enough

In a small monolithic project, the build target is usually obvious: one entry point, one artifact, one command to run it. A microservice project is different. A single repository may contain background services, synchronous initialization tools, protocol generation tasks, and deployment artifacts for different platforms.

If every service is maintained through handwritten scripts, several problems appear quickly:

ProblemSymptom
Scattered build targetsNew services are easy to forget in scripts
Platform differences leak upwardUnix and Windows process or resource rules become mixed together
Unstable startup orderInitialization tools and long-running services have no clear boundary
Ambiguous status checksMatching only by process name can kill or report the wrong process
Non-portable artifactsBinaries are generated without a runnable startup context

So gomake does not start by making a shorter compile command. It first defines stable delivery conventions: where source targets live, where tools live, where output goes, where configuration is read from, where logs are written, and which artifacts belong to the current platform.

Once those conventions exist, build and runtime orchestration no longer need to live as scattered script fragments across the repository.

02. A Path Model as the Project Coordinate System

The first abstraction in gomake is the path model.

It brings the project root, configuration directory, output directory, binary directory, log directory, archive directory, temporary directory, service source directory, and tool source directory into one shared vocabulary. Build tasks, startup tasks, and export tasks all see the same coordinates instead of rebuilding their own path logic.

This seems basic, but it matters a lot for cross-platform builds. Artifacts do not live in a single flat directory. They are split by operating system and architecture, by current-host output and target-platform output, by service binaries and tool binaries, and by exported archives. Without a shared path model, every task would have to rediscover these differences on its own.

gomake also allows callers to override the project root, output directory, configuration directory, service source directory, and tool source directory. That means it works with the standard OpenIM layout, but can also be embedded into Go projects with similar structure. In Kubernetes-style deployment, the configuration path can switch to a layout that better fits mounted configuration, avoiding local development paths leaking into runtime.

The path model solves a foundational problem: every task must agree on what “this project” means. Only when that coordinate system is stable can build and orchestration share state.

03. Directory Discovery Turns Services and Tools into Conventions

gomake does not require developers to manually register every binary target in a script. It discovers them through directory conventions.

The cmd directory represents background services. The tools directory represents synchronous tools. When a subdirectory follows the Go entry-point convention, it becomes a buildable target. Services and tools are emitted into different output areas and play different roles during startup.

That distinction is important.

Background services are long-running processes. They need instance indexes, configuration paths, process checks, and port observation. Tools are closer to startup prerequisites: initialization, migration, validation, or other preparatory actions. If a tool fails, the service startup should stop instead of booting a system whose dependencies are not ready.

Through directory discovery, gomake moves the question “what kind of executable is this?” out of command-line flags and into the project structure. A new service added under the convention becomes part of the build naturally. A new tool added under the convention enters the synchronous startup chain naturally. This reduces script maintenance and lowers the chance that collaborators forget to update a target list.

04. Cross-Platform Builds Are Not Just Command Loops

On the surface, cross-platform build means compiling for different GOOS and GOARCH combinations. In practice, there is more to manage.

gomake first resolves build options: whether CGO is enabled, whether release optimization is used, whether artifacts should be compressed, and whether target platforms come from explicit configuration or automatic host detection. It then resolves service and tool targets separately, emitting each platform’s artifacts into the right output directory. Windows executable naming, architecture-specific paths, and the service/tool split all flow through the same model.

Two design choices are especially useful.

First, configuration precedence is explicit. Build options passed from code take priority over environment variables, while environment variables act as defaults. That prevents hidden variables in CI or local shells from accidentally overriding the caller’s intent.

Second, build concurrency is intentionally restrained. If every service and every platform is compiled at maximum parallelism, CPU, memory, and disk I/O can be saturated, making builds less stable rather than faster. gomake controls concurrency based on machine capacity, balancing throughput with reliability.

So cross-platform build in gomake is not a list of repeated commands. It is an explainable artifact pipeline: discover targets, resolve platforms, separate service and tool output, then generate the runtime configuration foundation that the startup system can read.

05. start-config.yml Turns Build Output into a Runtime Plan

A successful build does not mean the system can run. gomake turns the discovered services and tools into a runtime plan such as start-config.yml.

This configuration focuses on three kinds of information: which services should stay running, which tools should run synchronously before startup, how many instances each service needs, and what runtime resource limits should be applied. By default, services start with one instance and a baseline file descriptor limit; users can adjust those values for their deployment.

This step connects the build system to the runtime system.

Without runtime configuration, a build script can only say “the files exist.” With runtime configuration, gomake can also answer “in what order should these files run, how many processes should exist, and which configuration path should be passed to them?” That is what makes it more than a normal build tool: build artifacts are not the endpoint; they become input for orchestration.

06. Startup Handles Determinism Before Concurrency

gomake splits startup into two kinds of actions: synchronous tools and background services.

Synchronous tools run first. They are suitable for actions that must finish before services start, such as preparing the environment or generating required data. If a tool fails, startup stops. This “preconditions first” approach avoids the half-successful state where services are running but the system they depend on is incomplete.

Background services start afterward. gomake uses the configured instance count, injects instance identity and configuration path, and launches services asynchronously. During a full startup, it first clears existing matching services, waits for old processes to exit, starts new processes, and then verifies that the expected count is present.

The key detail is process identification. gomake does not rely only on process names. It prefers matching by executable path. On machines where multiple versions or binaries with the same name may coexist, names alone are easy to misread; paths make it clearer whether a process is the service just built by this workflow.

After startup, gomake checks service counts and reports listening ports. Users do not just see that a command returned; they can inspect whether the system entered the expected runtime state.

07. stop and check Complete the Process Management Loop

Startup alone is not enough. Without stop and check, service orchestration remains incomplete.

gomake’s stop flow reads the same runtime configuration, locates the corresponding service processes, attempts normal termination first, and escalates when needed. After that, it keeps waiting and checking so that old processes do not remain after the command returns.

The check flow validates state from the other direction: how many service instances are declared in configuration, whether that number of processes exists on the machine, and which ports they listen on. This is useful for local development and integration environments because it gives a more direct entry point than guessing from logs.

Most importantly, start, stop, and check all use the same path and configuration model. The same rules used to find services during startup are used to stop them. The same output locations used during build are used to infer status during checks. This loop reduces state drift and prevents multiple scripts from fighting each other with different assumptions.

08. Platform Differences Stay at the Boundary

Cross-platform tools become hard to maintain when operating-system differences leak into the main workflow.

gomake keeps those differences at the boundary. Unix systems need file descriptor limit handling; Windows does not have the same semantics. Unix process priority and Windows priority classes are different models. Executable suffixes, process control, and environment behavior also vary by platform.

These differences do not pollute the top-level build and startup story. For users, the stable actions remain build, start, stop, and check. For the implementation, platform-specific behavior is isolated in platform-specific files and internal modules.

This is the right kind of boundary for infrastructure tools: expose stable semantics above, acknowledge system differences below. Cross-platform support does not mean pretending every system is identical; it means letting differences appear only where they must.

09. Unified Command Execution and Logging Make the Tool Observable

Build and orchestration tools eventually execute many external commands. If every task handles arguments, environment variables, working directories, standard input, output streams, error streams, and priority on its own, behavior quickly becomes inconsistent.

gomake wraps command execution in one model. A task describes what should run, where it should run, what environment it needs, and how output should be handled; the lower layer turns that into an actual process. Logging targets both the console and a shared file: the console gives immediate feedback, while the log file supports later debugging.

This gives gomake an important property for an engineering tool: failures are not silent. Build failures, tool failures, startup failures, and process-check failures can all be traced through a consistent output and logging model instead of being scattered across temporary print statements in unrelated scripts.

10. export Turns Artifacts into a Portable Delivery Package

Many build systems stop after generating binaries. Real delivery still has one more question: how do these files move to another machine and remain usable?

gomake’s export task builds the target artifacts, then packages service binaries, tool binaries, startup configuration, and a compiled Mage launcher into one archive. The exported result is not just a pile of binaries; it is a delivery unit with an entry point and runtime plan.

That lowers the deployment-side cost of understanding the repository. The receiver does not need to rediscover which files are services, which files are tools, or how they should be started. That information is preserved in the archive structure.

In other words, export extends gomake from “local build” to “portable runtime.” This is especially important for multi-platform delivery, where artifacts and launchers must remain matched to the platform they were built for.

11. Protocol Generation and Bootstrap Extend the Same Idea

Beyond the core build and service orchestration flow, gomake also provides protocol generation and first-run environment preparation.

The protocol generation task organizes generation around the Go module and protocol directories, making protocol updates accessible through the same Mage entry point. The bootstrap scripts solve the first-use problem: making sure Mage is available and Go dependencies are prepared.

These two capabilities are not the main delivery path, but they follow the same design direction: repeated, easy-to-forget, environment-sensitive engineering actions should move into one stable entry point. Users should not have to remember a collection of unrelated scripts; they should enter the same engineering vocabulary through Mage tasks.

12. Summary: gomake Solves Delivery Consistency

From the source structure, the most important design in gomake is not a single feature. It is the way Go project delivery is split into stable layers:

LayerProblem Solved
Path modelAll tasks share the same project coordinates
Directory discoveryServices and tools enter builds through structure
Platform modelMulti-OS and multi-architecture artifacts follow clear rules
Runtime configurationBuild output becomes a startup plan
Process controlStart, stop, and check form a closed loop
Export archiveLocal artifacts become a portable delivery package

Together, these layers turn gomake from a build entry point into a delivery workflow.

For a project like OpenIM, a build tool cannot stop at “it compiles.” It also has to keep delivery behavior consistent across developers, machines, and platforms. That is gomake’s architectural value: it reduces configuration through conventions, isolates platform differences behind one model, connects build output to process management through runtime configuration, and makes cross-platform build plus service orchestration one coherent workflow.