Goose ships as both a CLI tool and a static C library. The library exposes all of goose's internals -- config parsing, package management, build orchestration, lock files, and filesystem utilities -- so you can build your own package managers, build tools, or CI integrations in C.
Build and install goose from source:
git clone https://github.com/wess/goose.git
cd goose
make
make install # PREFIX=/usr/local by defaultThis installs:
| Path | Description |
|---|---|
PREFIX/bin/goose |
CLI binary |
PREFIX/lib/libgoose.a |
Static library |
PREFIX/include/goose/goose.h |
Main header |
PREFIX/include/goose/headers/*.h |
Internal headers |
To install to a custom location:
make install PREFIX=~/.localInclude the single umbrella header and link against the static library:
#include <goose/goose.h>Compile with:
cc myprogram.c -I/usr/local/include -L/usr/local/lib -lgoose -o myprogramIf you installed to a custom prefix:
cc myprogram.c -I~/.local/include -L~/.local/lib -lgoose -o myprogramThe library is organized into modules, each with its own header. The umbrella goose.h includes all of them.
Load, save, and create project configurations.
Config cfg;
// Load from goose.yaml
config_load("goose.yaml", &cfg);
// Access fields
printf("Project: %s v%s\n", cfg.name, cfg.version);
printf("Compiler: %s\n", cfg.cc);
printf("Dependencies: %d\n", cfg.dep_count);
// Create a default config
config_default(&cfg, "myproject");
// Save to disk
config_save("goose.yaml", &cfg);Key types:
typedef struct {
char name[128];
char git[512];
char version[64];
char path[512]; // local path dep (mutually exclusive with git)
} Dependency;
typedef struct {
char name[128];
char ext[16];
char command[256];
} Plugin;
typedef struct {
char name[128];
char command[512];
} Task;
typedef struct {
char name[128];
char version[64];
char description[256];
char author[128];
char license[64];
char src_dir[512];
char cc[64];
char cflags[256];
char ldflags[256];
char includes[32][512];
int include_count;
char sources[256][512];
int source_count;
Dependency deps[64];
int dep_count;
Plugin plugins[16];
int plugin_count;
Task tasks[32];
int task_count;
} Config;Functions:
| Function | Description |
|---|---|
config_load(path, cfg) |
Parse a goose.yaml file into a Config struct |
config_save(path, cfg) |
Write a Config struct to a goose.yaml file |
config_default(cfg, name) |
Initialize a Config with sensible defaults |
Manage the goose.lock file for reproducible dependency pinning.
LockFile lf;
// Load existing lock file
lock_load("goose.lock", &lf);
// Look up a specific package's SHA
const char *sha = lock_find_sha(&lf, "mathlib");
if (sha) {
printf("mathlib is pinned to %s\n", sha);
}
// Update or add an entry
lock_update_entry(&lf, "mathlib",
"https://github.com/user/mathlib.git",
"abc123def456");
// Save
lock_save("goose.lock", &lf);Functions:
| Function | Description |
|---|---|
lock_load(path, lf) |
Parse a goose.lock file |
lock_save(path, lf) |
Write a lock file to disk |
lock_find_sha(lf, name) |
Look up a package's pinned commit SHA |
lock_update_entry(lf, name, git, sha) |
Add or update a lock entry |
Fetch, remove, and update git-based dependencies.
Config cfg;
config_load("goose.yaml", &cfg);
LockFile lf;
lock_load("goose.lock", &lf);
// Fetch all dependencies (clones missing packages, checks locked SHAs)
pkg_fetch_all(&cfg, &lf);
// Save updated lock file
lock_save("goose.lock", &lf);
// Fetch a single dependency
Dependency dep = {0};
strncpy(dep.name, "mylib", sizeof(dep.name) - 1);
strncpy(dep.git, "https://github.com/user/mylib.git", sizeof(dep.git) - 1);
strncpy(dep.version, "v1.0", sizeof(dep.version) - 1);
pkg_fetch(&dep, "packages", &lf);
// Remove a package
pkg_remove("mylib", "packages");
// Update all packages to latest
pkg_update_all(&cfg, &lf);
// Extract package name from a git URL
char *name = pkg_name_from_git("https://github.com/user/mylib.git");
// name = "mylib"
// Get current commit SHA of a package
char sha[64];
pkg_get_sha("packages/mylib", sha, sizeof(sha));Functions:
| Function | Description |
|---|---|
pkg_fetch(dep, pkg_dir, lf) |
Clone or update a single dependency |
pkg_fetch_all(cfg, lf) |
Fetch all dependencies from config |
pkg_remove(name, pkg_dir) |
Delete a package from disk |
pkg_update_all(cfg, lf) |
Pull latest for all packages |
pkg_name_from_git(url) |
Extract package name from git URL |
pkg_get_sha(pkg_path, sha, size) |
Get HEAD commit SHA of a package |
Compile projects using the goose build pipeline.
Config cfg;
config_load("goose.yaml", &cfg);
// Run plugin transpilation (flex, bison, etc.)
build_transpile(&cfg);
// Build in debug mode
build_project(&cfg, 0);
// Build in release mode
build_project(&cfg, 1);
// Clean build artifacts
build_clean();Functions:
| Function | Description |
|---|---|
build_project(cfg, release) |
Full build: collect sources, resolve includes, compile |
build_transpile(cfg) |
Run plugin transpilers on matching source files |
build_clean() |
Remove the build directory |
Portable filesystem utilities.
// Create directories
fs_mkdir("build");
fs_mkdir("build/output");
// Check existence
if (fs_exists("goose.yaml")) {
printf("Config found\n");
}
// Remove directory tree
fs_rmrf("build");
// Write a file
fs_write_file("src/main.c", "#include <stdio.h>\nint main(void) { return 0; }\n");
// Collect all .c files recursively
char files[256][512];
int count = 0;
fs_collect_sources("src", files, 256, &count);
for (int i = 0; i < count; i++)
printf(" %s\n", files[i]);
// Collect files by extension
fs_collect_ext("src", ".h", files, 256, &count);Functions:
| Function | Description |
|---|---|
fs_mkdir(path) |
Create a directory (and parents) |
fs_exists(path) |
Check if a path exists |
fs_rmrf(path) |
Recursively delete a directory |
fs_write_file(path, content) |
Write string content to a file |
fs_collect_sources(dir, files, max, count) |
Recursively find all .c files |
fs_collect_ext(dir, ext, files, max, count) |
Recursively find files by extension |
Convert CMakeLists.txt files to goose.yaml format.
Config cfg;
// Parse CMakeLists.txt into a Config struct
cmake_to_config("CMakeLists.txt", &cfg);
printf("Converted project: %s\n", cfg.name);
// Or convert directly to a file
cmake_convert_file("CMakeLists.txt", "goose.yaml");Functions:
| Function | Description |
|---|---|
cmake_to_config(path, cfg) |
Parse CMakeLists.txt into a Config struct |
cmake_convert_file(cmake_path, yaml_path) |
Convert CMakeLists.txt to a goose.yaml file |
Goose's colored logging macros are available for consistent output:
info("Fetching", "mathlib v1.0"); // green tag, right-aligned
warn("Skipping", "already exists"); // yellow tag
err("failed to open %s", path); // red "error" tag, writes to stderrOutput format matches the goose CLI:
Fetching mathlib v1.0
Skipping already exists
error failed to open goose.yaml
A minimal tool that fetches dependencies and builds a project:
// fetchbuild.c
#include <goose/goose.h>
int main(int argc, char **argv) {
int release = 0;
if (argc > 1 && strcmp(argv[1], "--release") == 0)
release = 1;
Config cfg;
if (config_load(GOOSE_CONFIG, &cfg) != 0)
return 1;
info("Loading", "%s v%s", cfg.name, cfg.version);
LockFile lf = {0};
lock_load(GOOSE_LOCK, &lf);
if (pkg_fetch_all(&cfg, &lf) != 0) {
err("dependency fetch failed");
return 1;
}
lock_save(GOOSE_LOCK, &lf);
if (build_project(&cfg, release) != 0)
return 1;
info("Done", "build complete");
return 0;
}Compile:
cc fetchbuild.c -I/usr/local/include -L/usr/local/lib -lgoose -o fetchbuildRun in any goose project:
./fetchbuild
./fetchbuild --releaseA tool that lists all dependencies and their pinned commits:
// audit.c
#include <goose/goose.h>
int main(void) {
Config cfg;
if (config_load(GOOSE_CONFIG, &cfg) != 0)
return 1;
LockFile lf = {0};
lock_load(GOOSE_LOCK, &lf);
printf("Project: %s v%s\n", cfg.name, cfg.version);
printf("Dependencies: %d\n\n", cfg.dep_count);
for (int i = 0; i < cfg.dep_count; i++) {
const char *sha = lock_find_sha(&lf, cfg.deps[i].name);
printf(" %-20s %s\n", cfg.deps[i].name, cfg.deps[i].git);
if (cfg.deps[i].version[0])
printf(" %-20s version: %s\n", "", cfg.deps[i].version);
if (sha)
printf(" %-20s pinned: %.12s\n", "", sha);
else
printf(" %-20s pinned: (not locked)\n", "");
printf("\n");
}
return 0;
}A tool that creates new projects with custom templates:
// scaffold.c
#include <goose/goose.h>
int main(int argc, char **argv) {
if (argc < 2) {
err("usage: scaffold <name>");
return 1;
}
const char *name = argv[1];
// Create project directory
fs_mkdir(name);
char path[512];
snprintf(path, sizeof(path), "%s/src", name);
fs_mkdir(path);
// Write config
Config cfg;
config_default(&cfg, name);
strncpy(cfg.description, "Generated project", sizeof(cfg.description) - 1);
snprintf(path, sizeof(path), "%s/goose.yaml", name);
config_save(path, &cfg);
// Write main.c with custom template
snprintf(path, sizeof(path), "%s/src/main.c", name);
char src[1024];
snprintf(src, sizeof(src),
"#include <stdio.h>\n\n"
"int main(void) {\n"
" printf(\"Hello from %s!\\n\");\n"
" return 0;\n"
"}\n", name);
fs_write_file(path, src);
// Write .gitignore
snprintf(path, sizeof(path), "%s/.gitignore", name);
fs_write_file(path, "build/\npackages/\n");
info("Created", "%s", name);
return 0;
}The library provides path constants matching the goose CLI conventions:
| Constant | Value | Description |
|---|---|---|
GOOSE_VERSION |
from VERSION file |
Current goose version string |
GOOSE_CONFIG |
"goose.yaml" |
Default config filename |
GOOSE_LOCK |
"goose.lock" |
Default lock filename |
GOOSE_PKG_DIR |
"packages" |
Default package directory |
GOOSE_BUILD |
"build" |
Default build directory |
- All structs use fixed-size stack buffers. There is no heap allocation and nothing to free.
- Functions return
0on success and-1on error. - Error messages are printed to stderr via the
err()macro. - The library links against vendored libyaml, so no external dependencies are needed at runtime.
- Git operations use
system()andpopen()internally, sogitmust be available on the PATH.