Library / SDK
skywind3000/emake avatar
skywind3000/emake

emake: a single-file declarative build tool for C and C++ projects

你见过的最简单的 GCC/CLANG 项目构建工具,定义式构建,比命令式更简单

909 stars122 forksPythonGPL-2.0

At a glance

What is it?
emake is a Python build tool for C, C++ and Objective-C that reads a short configuration file, or comments inside the source itself, and drives gcc, mingw or clang from there. The trade is explicit flexibility for a very small configuration surface.
Who is it for?
Pick emake when a project is small enough that a dozen configuration lines describe it, when the team wants one file it can read end to end, and when the platforms in play are covered by gcc, mingw or clang. Do not pick it when the build needs conditional logic per target, custom generator steps, or the package and toolchain reach of CMake, and do not vendor emake.py into a closed-source product without reading GPL-2.0 obligations first.
Can I use it commercially?
Yes, with conditions. GPL-2.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 39 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One Python file, and configuration that lives next to the code

emake ships as a single module, emake.py, and the repository root shows how little surrounds it: emake.py, history.txt, pyproject.toml, LICENSE, README.md, a .travis.yml, a .python-version and a small sample/ directory holding build.bat, build.sh, testmain.c, testsrc1.c and testsrc1.h. There is no generator, no project file to commit, and no separate build system binary to install.

The design is declarative. Instead of writing steps, you declare what the project is: which source files exist, which flags apply, which libraries get linked, what the output should be called. The configuration keys are fixed and small. src lists sources, inc and lib set include and library directories, link names libraries, flag, cflag and cxxflag carry compiler options, define sets macros, mode selects the output kind, out names the binary, int sets the scratch directory, and flnk and wlnk pass arguments at link time. A second group handles the rest of a build: package imports a pkg-config package, import pulls in a named section of emake.ini, export produces a .lib for MSVC when mode is dll, and preload, prebuild, prelink and postbuild attach shell commands to the four points in the build where a hook can run.

The project file is normally called .mak, and it uses a key per line with a colon, which is a syntax the tool can parse in a few dozen lines of Python.

Installing emake: pip, one wget, or a Windows batch file

The shortest path is a package install, and the distribution name on PyPI differs from the import name, which trips people up:

bash
pip install emake-build

The package is published as emake-build and installs a single command:

bash
emake -h
emake main.cpp

One caveat is stated plainly in the documentation: pip installs emake itself and nothing else, so gcc, clang and mingw still have to be on your machine already.

The alternative is to fetch the one file and run it with an install flag:

bash
wget http://skywind3000.github.io/emake/emake.py
sudo python emake.py -i

That copies the script to /usr/local/bin so the emake command is available on the path. The Windows path is manual: download emake.py, place it in the root of your mingw installation so emake can find gcc, add that directory to PATH, and create an emake.cmd file next to it containing:

batch
@echo off
d:\dev\python311\python.exe d:\dev\mingw\emake.py %*

The two paths in that batch file are yours to change. The command exists so you type emake rather than the longer python emake.py invocation.

A three source project in six lines of configuration

The quick start assumes foo.c, bar.c and main.c build into an executable called main, and the configuration that describes it is this:

make
mode: exe

src: foo.c
src: bar.c
src: main.c

Building it is one command:

bash
emake main.mak

That is the whole mental model: declare, then run. Source lists accept wildcards, so the same key can pull in a whole tree:

make
src: core/*.c
src: source/*.cpp

Everything else in the configuration table behaves the way you would expect from a compiler driver. flag carries options common to every language, cflag and cxxflag narrow them per language, and inc and lib add search directories in the same comma-separated style as src. link names libraries the way gcc's -l flag does, and the documentation is explicit that compiling C++ means linking stdc++. A full configuration looks like this:

make
mode: exe

flag: -Wall, -O3, -g

link: m, pthread, stdc++

inc: /usr/local/opt/jdk/include
inc: /usr/local/opt/jdk/include/linux

lib: /usr/local/opt/jdk/lib

src: foo.c
src: bar.c
src: main.c

The output kind is chosen with mode: exe produces an executable, lib a static library, dll a shared library, and win a Windows program with no console window. Two more keys handle housekeeping: out renames the output, and int moves the intermediate .o files out of your working tree, where int: objs keeps them in a folder you can delete to clean the project.

The zero project file mode: build config as //! comments

If even a short .mak file feels like ceremony, emake can read its configuration out of the source comments. Lines beginning with //! are treated as configuration directives, so a single main.cpp carries its own build definition:

cpp
#include <stdio.h>

//! mode: exe
//! flag: -Wall, -O3, -g
//! link: m, pthread
//! src: foo.c, bar.c
int main(void) {
    ...
    return 0;
}

The source file the comments live in is added to the build automatically, so the src line does not repeat it. You then point emake at the source file rather than at a project file:

bash
emake main.cpp

This mode is the reason the tool feels close to an editor workflow rather than a build script, and it suits throwaway experiments where a separate project file would be the heavier artifact. The same directives that live in a .mak file work here, so the two approaches do not limit each other: a project can start with comments and grow into a file, or a file can hold a few keys while sources carry the rest.

One small option exists for people whose editor parses paths badly. Passing --abs makes error messages carry absolute paths:

bash
emake --abs main.cpp

Relative paths in a compiler error are often unresolvable in an IDE, and this flag trades nothing for fixing that.

What declarative costs: the cases emake will not cover

The comparison emake makes with CMake and GNU Make is the honest core of the project, and it cuts both ways. The documentation calls CMake and GNU Make imperative tools, where the build script is a program capable of arbitrary branching, powerful but complicated. emake is declarative in the way an IDE is declarative: you set the files, the compiler flags and the link flags, and the tool works out the rest. The stated cost is flexibility, and the stated audience is small and medium projects plus personal experiments.

That boundary shows up in practice. There is no conditional logic inside a project file, no way to compute a source list from another program, and no portable way to express a per-target variant other than through the ini configuration and its named sections. The docs also do not describe a dependency cache, a distributed build, or a configure step of any kind. Third-party packages arrive through pkg-config, vcpkg or by hand, and the configuration table exposes only package, which imports a pkg-config package and sets its include and library paths and link options, plus pcflag for arguments passed to pkg-config itself. There is no equivalent key for vcpkg in that table, even though the documentation lists vcpkg as a supported import path and links to a wiki page on the integration.

Another limitation is quieter. The documentation is written in Simplified Chinese, and the packaging metadata lists Natural Language :: Chinese (Simplified) as the only natural language. For a tool whose selling point is reading a small configuration, a second-language README is a real adoption cost rather than a footnote.

Toolchains, packages and hooks through emake.ini

Above the project file sits a configuration file, emake.ini, that holds the settings a project should not repeat. It is organised in sections, and the default section carries a name list that other parts of the setup read:

ini
[default]
name=android,posix,ar

Named sections hold toolchain settings, and a project can pull one in with the import key, which imports a configuration from a section of emake.ini other than default. The include and library directories a project needs, the compiler driver to call, and cross-compilation settings all live in that file, which is why the sample directory at the repository root includes both build.sh and build.bat: the same project builds on Linux and macOS and on Windows.

The cross-compilation story is documented at a high level rather than in detail. The feature list claims support for building iOS, Android and asm.js projects, and names the toolchains gcc, mingw and clang, the languages C, C++, ObjC, ObjC++ and ASM, and the systems Windows, Linux, Mac OS X and FreeBSD. The README links to a wiki page on how to configure cross compilation rather than showing the configuration itself.

Around the compilation itself sit the hooks. preload, prebuild, prelink and postbuild attach shell commands to the load, compile, link and post-link stages, and environ sets environment variables for the build. echo prints text, color sets the colour of that output, and export emits a .lib file for MSVC when the mode is dll. That is a small, legible surface, and it is also the boundary: anything you need that is not in this list means writing a shell command into one of the four hooks.

Licence, packaging and where the project actually stands

The licence field on the repository says GPL-2.0, and the packaging metadata points at the LICENSE file and carries the GNU General Public License v2 classifier. For a tool whose whole point is that it is one readable Python file, that licence has a practical consequence worth thinking about: anyone who modifies emake.py and ships the result inherits the obligations attached to the file, which is a question for your own legal review rather than something this article can answer.

The package itself is unusually lean. pyproject.toml requires setuptools>=61, declares requires-python >=3.8, and lists no runtime dependencies at all. It publishes a single module through py-modules = ["emake"] and one console entry point, emake = "emake:main". The version currently in that file is 3.7.11, and the project has no tagged GitHub releases, so the version in pyproject.toml and the changelog in history.txt are the record of what changed.

On activity, the last push to the repository was on 2026-09-14. The project states that the first version appeared in 2009 and that the team has used it over the years to build server, client, iOS, Android and Flash projects. What is verifiable here is narrower: one Python file, a dependency-free package, a configuration surface that has not changed shape across those years, and a repository that received its most recent commit on 2026-09-14.

Editorial conclusion

Pick emake when a project is small enough that a dozen configuration lines describe it, when the team wants one file it can read end to end, and when the platforms in play are covered by gcc, mingw or clang. Do not pick it when the build needs conditional logic per target, custom generator steps, or the package and toolchain reach of CMake, and do not vendor emake.py into a closed-source product without reading GPL-2.0 obligations first. Before you start, check three things: which compiler driver emake finds on your PATH, whether pip install emake-build gives you a working emake command on your platform, and whether the emake.ini section names your target configuration resolves. The last push to the repository was on 2026-09-14, the project has no tagged releases, and the current version sits in pyproject.toml as 3.7.11.

Frequently asked questions

How do I install emake on Linux or macOS?

Either run pip install emake-build, which installs the emake command but no C/C++ toolchain, or fetch the single script with wget http://skywind3000.github.io/emake/emake.py and run sudo python emake.py -i, which copies it to /usr/local/bin.

What is the difference between the emake-build package and emake.py?

emake-build is the PyPI distribution name for the same code that lives in the repository as emake.py. The distribution installs a console command named emake, and the pip path does not install gcc, clang or mingw for you.

Can I build a C/C++ project without writing a separate build file?

Yes. Configuration keys can be written as //! comments inside the source file, and the file that carries them is added to the build automatically. Running emake main.cpp then compiles the project without any .mak file present.

How does emake find third-party libraries?

Third-party packages can be imported through pkg-config, through vcpkg, or by hand. The package key imports a pkg-config package and sets its include and library directories plus its link options, and pcflag passes extra arguments to pkg-config.

What licence is emake released under and what does it mean for my project?

The licence field on the repository says GPL-2.0, and the packaging metadata points at the LICENSE file with a GNU General Public License v2 classifier. Whether that affects your distribution depends on how you use or modify the file, which is a question for your own legal review.

Which compilers and operating systems does emake support?

The feature list names gcc, mingw and clang as supported tools, C, C++, ObjC, ObjC++ and ASM as supported languages, and Windows, Linux, Mac OS X and FreeBSD as supported systems. Cross compilation for Android NDK, iOS and asm.js is listed, with details in the project wiki rather than in the main documentation.

Official sources

  1. Issues
  2. License: GPL-2.0
  3. README
  4. skywind3000/emake on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/skywind3000-emake.svg)](https://hysenlabs.com/projects/skywind3000-emake)

Was this useful?