--- title: Using Bazel to create Minecraft modpacks description: An overview of how I automated the build process for CorePack date: 2020-10-24 written: 2020-09-27 tags: bazel workflow git minecraft extra: excerpt: I decided to modernize my system for producing builds of my personal Minecraft modpack using the Bazel buildsystem. redirect_from: - /post/XlA00k24/ - /XlA00k24/ aliases: - /blog/2020/10/24/corepack-development - /blog/corepack-development --- *All content of this post is based around the work I did [here](https://github.com/Ewpratten/corepack)* Back in [2012](https://minecraft.gamepedia.com/Java_Edition_1.2.5), I got in to Minecraft mod development, and soon after, put together an almost-vanilla client-side modpack for myself that mainly contained rendering, UI, and quality-of-life tweaks. While this modpack never got published, or was even given a name, I kept maintaining it for years until I eventually stopped playing Minecraft just before the release of Minecraft [`1.9`](https://minecraft.gamepedia.com/Java_Edition_1.9) (in 2016). I had gotten so used to the features of this modpack, that playing truly vanilla Minecraft didn't feel correct. Recently, a few friends invited me to join their private Minecraft server, and despite having not touched the game for around four years, I decided to join. This was a bit of a mistake on their part, as they now get the pleasure of someone who used to main [`1.6.4`](https://minecraft.gamepedia.com/Java_Edition_1.6.4) constantly walking up to things and asking *"What is this and how does it work?"*. I have started to get used to the very weird new collection of blocks, completely reworked command system, over-complicated combat system, and a new rendering system that makes everything "look wrong". One major thing was still missing though, *where was my modpack?* I set out to rebuild my good old modpack (and finally give it a name, *CorePack*). Not much has changed, most of the same rendering and UI mods are back, along with the same [GLSL](https://en.wikipedia.org/wiki/OpenGL_Shading_Language) shaders, and similar textures. Although, I did decide to take a *"major step"* and switch from the [Forge Mod Loader](http://files.minecraftforge.net/) to the [Fabric Loader](https://fabricmc.net/), since I prefer Fabric's API. ## Curseforge & Bazel I don't remember [Curseforge](https://curseforge.com/) existing back when I used to play regularly. It is a huge improvement over the [PlanetMinecraft](https://www.planetminecraft.com/) forums, as curse provides a clean way to access data about published Minecraft mods, and even has an API! Luckily, since I switched the modpack to Fabric, every mod I was looking for was available through curse (although, it seems [NEI](https://www.curseforge.com/minecraft/mc-mods/notenoughitems) is a thing of the past). My main goal for the updated version of CorePack was to design it in such a way I could make a CI pipeline generate new releases for me when mods are updated. This requires programmatically pulling information about mods, and their JAR files using a buildsystem script. Since this project involves working with a large amount of data from various external sources, I once-again chose to use [Bazel](https://bazel.build), a buildsystem that excels at these kinds of projects. While Curseforge provides a very easy to use API for working with mod data, @Wyn-Price (a fellow mod developer) has put together an amazing project called [Curse Maven](https://www.cursemaven.com/) that I decided to use instead. Curse Maven is a serverless API that acts much like my [Ultralight project](@/blog/2020-09-17-Ultralight-writeup.md). Any request for an artifact to Curse Maven will be redirected, and served from the [Curseforge Maven server](https://authors.curseforge.com/knowledge-base/projects/529-api#Maven) without the need for me to figure out the long-form artifact identifiers used internally by curse. Curse Maven makes loading a mod (in this case, [`fabric-api`](https://www.curseforge.com/minecraft/mc-mods/fabric-api)) into Bazel as easy as: ```python # WORKSPACE # Load bazel_maven_repository http_archive( name = "maven_repository_rules", strip_prefix = "bazel_maven_repository-1.2.0", type = "zip", urls = ["https://github.com/square/bazel_maven_repository/archive/1.2.0.zip"], ) load("@maven_repository_rules//maven:maven.bzl", "maven_repository_specification") load("@maven_repository_rules//maven:jetifier.bzl", "jetifier_init") jetifier_init() # Declare any mods as maven artifacts maven_repository_specification( name = "maven", artifacts = { "curse.maven:fabric-api:3049174": {"insecure": True} }, repository_urls = [ "https://www.cursemaven.com", ], ) ``` The above snippet uses a Bazel ruleset developed by [Square, Inc.](https://squareup.com/ca/en) called [`bazel_maven_repository`](https://github.com/square/bazel_maven_repository). ## Modpack configuration Since my pack is designed for use with [MultiMC](https://multimc.org/), two sets of configuration files are needed. The first set tells MultiMC which versions of [LWJGL](https://www.lwjgl.org/), Minecraft, and Fabric to use, and the second set are the in-game config files. Many of these files contain information that I would like to modify from Bazel during the modpack build step. Luckily, the [Starlark](https://docs.bazel.build/versions/master/skylark/language.html) core library comes with an action called [`expand_template`](https://docs.bazel.build/versions/2.0.0/skylark/lib/actions.html#expand_template). `expand_template` is basically a find-and-replace tool that will perform substitutions on files. Since this is an action, and not a rule, it must be wrapped with a small rule declaration: ```python # tools/template.bzl def expand_template_impl(ctx): ctx.actions.expand_template( template = ctx.file.template, output = ctx.outputs.out, substitutions = { k: ctx.expand_location(v, ctx.attr.data) for k, v in ctx.attr.substitutions.items() }, is_executable = ctx.attr.is_executable, ) expand_template = rule( implementation = expand_template_impl, attrs = { "template": attr.label(mandatory = True, allow_single_file = True), "substitutions": attr.string_dict(mandatory = True), "out": attr.output(mandatory = True), "is_executable": attr.bool(default = False, mandatory = False), "data": attr.label_list(allow_files = True), }, ) ``` In a `BUILD` file, template rules can be defined as follows: ```python # BUILD load("//tools:template.bzl", "expand_template") expand_template( name = "my_config", template = "config.json.in", out = "config.json", substitutions = { "TEST_SUBS": "hello world" } ) ``` Using the following example file as `config.json.in`, this rule would have the following effect: ```js // config.json.in { "key": "TEST_SUBS" } // config.json { "key": "hello world" } ``` ## Packaging Once mods are loaded, and configuration files are defined in the buildsystem, I use a large number of [`filegroup`](https://docs.bazel.build/versions/master/be/general.html#filegroup) and [`genrule`](https://docs.bazel.build/versions/master/be/general.html#genrule) rules to set up a directory hierarchy in the workspace, and wrap everything in a call to [`zipper`](https://sourcegraph.com/github.com/v2ray/ext/-/blob/bazel/zip.bzl#L23:25) to package the modpack into a ZIP file. Finally, I use [GitHub Actions](https://github.com/features/actions) to automatically run the buildscript, and publish the resulting MultiMC instance zip to the [GitHub repo](https://github.com/Ewpratten/corepack) for this project.