Examples

The Makex Makexfile

The following is the Makex File used to build and deploy Makex.

You might get some ideas here:

#!makex
"""
Note: This file must be run with the MAKEX_DEVELOPER environment variable to something True.

Build/Deploy

- use the pypi task to build/deploy the latest version to pypi. you'll need twine set up properly.
- use the upload-documents task to upload the latest version of the documentation.

"""

# Python interpreter to use to create a virtual environment:
PYTHON = "python3"

VENV = task_path("venv")
VENV_BIN = VENV / "bin"

# NOTE: This must be updated on new release versions (YYYYMMSS[equence]).
VERSION = "20250102"

CHANGE = Environment.get("CHANGE", "")

PYPROJECT_TOML = source() / "python" / "pyproject.toml"

task(
    name="hello-world",
    steps=[
        print("Hello World!"),
    ],
)

task(
    name="documents-html",
    requires=[
        ":venv",
        find("documents/source"),
        find("python"),
        "README.md",
    ],
    outputs=[
        "html/index.html",
    ],
    # Set the environment for future actions.
    environment={
        "BUILDDIR": task_path("documents-html"),
        "LC_ALL": "C",
        "RELEASE": VERSION,
    },
    steps=[
        shell(
            f"source {VENV}/bin/activate",
            f"make -e -C documents clean html",
        ),
        print(f"Documents available at file://{self.path}/html/index.html")
        # all three of these are the same:
        #shell(f"xdg-open $PWD/_output_/documents-html/html/index.html")
        #shell(f"xdg-open {path('documents-html')}/html/index.html")
        #execute("xdg-open", path('documents-html') / "html/index.html")
    ],
)

task(
    name="upload-documents",
    requires=[
        ":documents-html",
    ],
    steps=[
        execute(
            "upload-documents", "makex", VERSION, "--latest", task_path("documents-html") / "html"
        ),
    ],
)

# stage a directory for a source package
task(
    name="source-package",
    requires=[
        find("python", glob("**/*.py")),
        ":build-completions",
    ],
    steps=[
        copy("python", exclude=[glob("**requirements*.txt"), glob("**pyproject.toml")]),
        copy(PYPROJECT_TOML),
        #mirror(["documents/Makefile"]), # "documents/source"
        copy("Makexfile"),
        copy("README.md"), #archive(f"makex-{VERSION}-source.zip"),
        execute("metacompany-generate-source-license", "--output", self.path / "LICENSE.md"),
        copy(
            ["scripts/completions/makex.bash", "scripts/completions/makex.zsh"],
            "python/makex/data/completions"
        ),
        write("python/makex/version.py", f"""VERSION="{VERSION}"\nCHANGE="{CHANGE}"\n"""),
    ],
)

# Build and deploy the latest version
task(
    name="pypi",
    requires=[
        ":source-package",
        ":venv",
    ],
    steps=[
        #shell(
        # build the wheel from the source distribution.
        # python -m build --wheel --outdir {path('pypi')} .
        #    f"metacompany-twine {path('pypi')}/*",
        #),
        execute(
            VENV_BIN / "pyproject-build",
            "--sdist",
            "--outdir",
            task_path('source-package'),
            task_path('source-package')
        ),
        execute("metacompany-twine", task_path('source-package')),
    ],
)

PEX_ROOT = task_path("install-pex")

PEX_ARGS = []
PEX_ARGS += [f"--pex-root", f"{PEX_ROOT}"]
PEX_ARGS += ["-P", "makex@python"]
PEX_ARGS += ["-m", "makex"]
PEX_ARGS += ["--sh-boot"]
PEX_ARGS += ["--no-wheel", "--pip-version=latest"]
PEX_ARGS += ["--requirement", PEX_ROOT / "requirements.txt"]
# XXX: this doesn't work for some reason
#PEX_ARGS+= ["--interpreter-constraint", "CPython>=3.9"]

task(
    name="install-pex",
    requires=[
        ":venv", #Reference("venv"),
        ":source-package",
    ],
    steps=[
        execute(
            "pip-requirements", "txt", "--required", PYPROJECT_TOML, PEX_ROOT / "requirements.txt"
        ),
        execute(
            VENV_BIN / "pex",
            PEX_ARGS,
            "--output-file",
            self.outputs.executable,
        ),
    ],
    outputs={
        "executable": home() / ".local/bin/makex",
    }
)

# create a venv
task(
    name="venv",
    requires=[
        PYPROJECT_TOML,
    ],
    outputs=[
        VENV / "pyvenv.cfg",
    ],
    steps=[
        execute(PYTHON, "-m", "venv", "--clear", VENV),
        execute(VENV_BIN / "pip", "install", "--upgrade", "pip"),
        execute(
            "pip-requirements",
            "install",
            "--pip",
            VENV_BIN / "pip",
            "--all",
            "python/pyproject.toml"
        ),
    ],
)

task(
    name="mypy",
    steps=[
        ## || exit 0 because mypy returns a non-zero which causes makex to report errors
        shell(
            f"source {VENV}/bin/activate",
            "pip install mypy",
            # --cache-dir= because we don't want to pollute the source with .mypy_cache directories
            # mypy returns a non-zero exit code so we have to use || exit 0
            f"mypy --cache-dir={task_path('mypy')} --follow-imports=silent --config-file python/mypy.ini --explicit-package-bases --show-absolute-path -p makex || exit 0",
        )
    ]
)

task(
    name="build-completions",
    requires=[
        ":venv",

 # completions may change here:
        "python/makex/__main__.py",
    ],
    steps=[
        shell(
            f"source {VENV}/bin/activate",
            # TODO: patch shtab to take preamble from file
            #"preamble=\"$(cat scripts/bash-completion/makex-preamble.bash)\"",
            #f"cd python && shtab --shell=bash -u makex.__main__.parser --preamble \"${{preamble}}\" | tee {source('scripts/bash-completion/makex.bash')}", #
            "cd python",
            f"{PYTHON} -m makex completions --shell bash {self.outputs.bash}", #
            f"{PYTHON} -m makex completions --shell zsh {self.outputs.zsh}", #
        )
    ],
    outputs={
        "bash": source('scripts/completions/makex.bash'),
        "zsh": source('scripts/completions/makex.zsh'),
    }, # makex completions --print bash > ~/.local/share/bash-completion/completions/makex
)

task(
    name="test",
    requires=[":venv"],
    steps=[
        shell(
            f"source {VENV}/bin/activate",
            f"pytest -o cache_dir={self.path}/pytest python",
        )
    ]
)


# test creating a task using a macro
# todo: test creating action or list of actions with a macro
@macro
def nuitka_target_command_line(
    name,
    PYTHON_PACKAGE,
    APPLICATION_NAME,
    pythonpath,
    main,
    requires=None,
    VENV_BIN=None,
    extra=None,
):
    """ Compile a command line application for nuitka. """
    BUILD_NUITKA = task_path(name)

    NUITKA_ARGS = []
    NUITKA_ARGS += [f"--include-package={PYTHON_PACKAGE}"]
    NUITKA_ARGS += [f"--report={BUILD_NUITKA}/nuitka-build-report.xml"]
    NUITKA_ARGS += ["--follow-imports", "--disable-console"]
    NUITKA_ARGS += [f"--output-dir={BUILD_NUITKA}"]
    NUITKA_ARGS += [f"--output-filename={APPLICATION_NAME}"]
    #NUITKA_ARGS += [f"--include-package-data={PYTHON_PACKAGE}.data.completions"]
    NUITKA_ARGS += ["--python-flag=-O,nosite,no_docstrings"]
    NUITKA_ARGS += ["--show-anti-bloat-changes"]
    NUITKA_ARGS += ["--nofollow-import-to=*.tests"]
    NUITKA_ARGS += ["--nofollow-import-to=*_test.py"]
    #NUITKA_ARGS += ["--nofollow-import-to=shtab"]
    NUITKA_ARGS += ["--nofollow-import-to=pytest"]
    NUITKA_ARGS += ["--standalone", "--onefile"]
    NUITKA_ARGS += ["--clang"]
    NUITKA_ARGS += ["--static-libpython=yes"]

    NUITKA_ARGS += ["--experimental=no-outside-dependencies"]
    NUITKA_ARGS += ["--prefer-source-code"]
    NUITKA_ARGS += ["--warn-unusual-code", "--warn-implicit-exceptions"]
    NUITKA_ARGS += extra

    task(
        name=name,
        requires=requires,
        environment={
            "PYTHONPATH": pythonpath,
            "CCFLAGS": "-march=native -mtune=native -Os",
        },
        steps=[
            execute(VENV_BIN / "nuitka3", NUITKA_ARGS, main),
        ],
    )


nuitka_target_command_line(
    name="nuitka2",
    PYTHON_PACKAGE="makex",
    APPLICATION_NAME="makex",
    pythonpath=source("python"),
    main=source("python/makex/__main__.py"),
    requires=[":venv"],
    VENV_BIN=VENV_BIN,
    extra=[
        f"--include-package-data=makex.data.completions",
        "--nofollow-import-to=shtab",
    ]
)
# TODO: call(":nuitka_target_command_line", ...)

BUILD_NUITKA = task_path("nuitka")
PYTHON_PACKAGE = "makex"
APPLICATION_NAME = "makex"

NUITKA_ARGS = []
NUITKA_ARGS += [f"--include-package={PYTHON_PACKAGE}"]
NUITKA_ARGS += [f"--report={BUILD_NUITKA}/nuitka-build-report.xml"]
NUITKA_ARGS += ["--follow-imports", "--disable-console"]
NUITKA_ARGS += [f"--output-dir={BUILD_NUITKA}"]
NUITKA_ARGS += [f"--output-filename={APPLICATION_NAME}"]
NUITKA_ARGS += [f"--include-package-data={PYTHON_PACKAGE}.data.completions"]
NUITKA_ARGS += ["--python-flag=-O,nosite,no_docstrings"]
NUITKA_ARGS += ["--show-anti-bloat-changes"]
NUITKA_ARGS += ["--nofollow-import-to=*.tests"]
NUITKA_ARGS += ["--nofollow-import-to=*_test.py"]
NUITKA_ARGS += ["--nofollow-import-to=shtab"]
NUITKA_ARGS += ["--nofollow-import-to=pytest"]
NUITKA_ARGS += ["--standalone", "--onefile"]
NUITKA_ARGS += ["--clang"]
#NUITKA_ARGS += ["--static-libpython=yes"]
NUITKA_ARGS += ["--experimental=no-outside-dependencies"]
NUITKA_ARGS += ["--prefer-source-code"]
NUITKA_ARGS += ["--warn-unusual-code", "--warn-implicit-exceptions"]

task(
    name="nuitka",
    requires=[
        ":venv",
    ],
    environment={
        "PYTHONPATH": source("python"),
        "CCFLAGS": "-march=native -mtune=native -Os",
    },
    steps=[
        execute(VENV_BIN / "nuitka", NUITKA_ARGS, source("python/makex/__main__.py")),
    ]
)

task(
    name="publish-documents",
    requires=[":documents-html"],
    steps=[
        execute(
            "metacompany-upload-documents",
            "--latest",
            "makex",
            VERSION,
            task_path("documents-html") / "html"
        )
    ]
)

### Build RPM:

# RPM Parameters
# Release is typically unchanged
# Set MAKEX_RPM_DIST to override the dist
RPM_NAME = "makex"
RPM_VERSION = VERSION
BUILD_ROOT = task_path("rpm")
RPMBUILD_FLAGS = ["--define", f"_topdir {BUILD_ROOT}"]
RPMBUILD_FLAGS += ["--define", f"_rpmdir {BUILD_ROOT}"]
RPMBUILD_FLAGS += ["--define", "_srcrpmdir %{_rpmdir}"]
RPMBUILD_FLAGS += ["--define", f"version {RPM_VERSION}"]

RPM_SOURCES_FOLDER = task_path("rpm") / "SOURCES"

task(
    name="rpm",
    requires=[":source-package"],
    steps=[
        execute("mkdir", RPM_SOURCES_FOLDER),
        copy("packaging/makex.sh", "SOURCES"),
        shell(
            f"cd {task_path('source-package')} && zip -r {RPM_SOURCES_FOLDER}/makex-source.zip *"
        ),

        # TODO: use this instead
        #archive(
        #    path=task_path("rpm") / "SOURCES/makex-source.zip",
        #    prefix=f"makex-{VERSION}",
        #    files=[
        #        "python"#
        #    ]
        #),
        execute("rpmbuild", RPMBUILD_FLAGS, "-v", "-ba", "packaging/makex.spec"),
    ]
)

# test out pyoxidizer
task(
    name="pyoxidizer",
    steps=[execute("pyoxidizer", "build", "--var", "build_path", task_path("pyoxidizer"))]
)