183 lines
7.5 KiB
Nix
183 lines
7.5 KiB
Nix
{ pkgs, lib }:
|
|
|
|
let
|
|
# Static per-Minecraft-version pins. Real "latest-recommended" resolution
|
|
# would need network access at eval time, so for now every supported
|
|
# targetVersion needs an entry here.
|
|
versionTable = {
|
|
"1.21.1" = {
|
|
neoVersion = "21.1.244";
|
|
minecraftVersionRange = "[1.21.1]";
|
|
loaderVersionRange = "[1,)";
|
|
parchmentMinecraftVersion = "1.21.1";
|
|
parchmentMappingsVersion = "2024.11.17";
|
|
templateDir = "1.21.x";
|
|
};
|
|
};
|
|
trace = value: builtins.trace value value;
|
|
in
|
|
|
|
{ modPath }:
|
|
|
|
let
|
|
evaluated = lib.evalModules {
|
|
modules = [ ../modules/mod-meta.nix ../modules/mod-blocks.nix modPath ];
|
|
};
|
|
cfg = evaluated.config;
|
|
|
|
versionInfo =
|
|
versionTable.${cfg.targetVersion} or (throw
|
|
"snowy: unsupported targetVersion '${cfg.targetVersion}'. Supported versions: ${lib.concatStringsSep ", " (lib.attrNames versionTable)}");
|
|
|
|
resolvedNeoVersion =
|
|
if cfg.frameworkVersion == "latest-recommended"
|
|
then versionInfo.neoVersion
|
|
else cfg.frameworkVersion;
|
|
|
|
# Hand-written files (customClass, blocks.*.customClass, ...) are given as plain
|
|
# strings, e.g. "./src/CreateSilicon.java" -- snowy never reads their content or
|
|
# location, it only needs the class name (the file's basename) to generate the
|
|
# matching Base class and wire up imports/registration on the generated side. The
|
|
# actual file is expected to already live at the real Gradle-conventional path
|
|
# (src/main/java/{package}/...) in the project directory the shellHook runs from.
|
|
classNameFromPath = p: if p == null then null else lib.removeSuffix ".java" (baseNameOf p);
|
|
|
|
splitBlockId = id:
|
|
let parts = lib.splitString ":" id;
|
|
in
|
|
assert lib.assertMsg (builtins.length parts == 2)
|
|
"snowy: block id '${id}' must be of the form '<namespace>:<path>'";
|
|
{ namespace = builtins.elemAt parts 0; path = builtins.elemAt parts 1; };
|
|
|
|
blocksList = lib.mapAttrsToList
|
|
(id: blockCfg:
|
|
let
|
|
split = splitBlockId id;
|
|
hasEnoughTextures = blockCfg.textures != null && (
|
|
blockCfg.textures.all != null
|
|
|| (blockCfg.textures.top != null && blockCfg.textures.bottom != null
|
|
&& (blockCfg.textures.side != null
|
|
|| (blockCfg.textures.north != null && blockCfg.textures.south != null
|
|
&& blockCfg.textures.east != null && blockCfg.textures.west != null)))
|
|
);
|
|
in
|
|
assert lib.assertMsg (split.namespace == cfg.modid)
|
|
"snowy: block '${id}' namespace must match modid '${cfg.modid}'";
|
|
assert lib.assertMsg (!(blockCfg.textures != null && blockCfg.customModel))
|
|
"snowy: block '${id}': textures and customModel are mutually exclusive";
|
|
assert lib.assertMsg (blockCfg.customModel || hasEnoughTextures)
|
|
"snowy: block '${id}': textures must set 'all', or 'top'+'bottom'+('side' or all four side faces), unless customModel is set";
|
|
assert lib.assertMsg (!(blockCfg.item.texture != null && blockCfg.item.customModel))
|
|
"snowy: block '${id}': item.texture and item.customModel are mutually exclusive";
|
|
{
|
|
inherit id;
|
|
inherit (split) path;
|
|
displayName = blockCfg.displayName;
|
|
properties = blockCfg.properties;
|
|
customClassName = classNameFromPath blockCfg.customClass;
|
|
textures = blockCfg.textures;
|
|
customModel = blockCfg.customModel;
|
|
item = blockCfg.item;
|
|
blockEntity =
|
|
if blockCfg.blockEntity == null then null
|
|
else {
|
|
customClassName = classNameFromPath blockCfg.blockEntity.class;
|
|
tickerClient = blockCfg.blockEntity.ticker.client;
|
|
tickerServer = blockCfg.blockEntity.ticker.server;
|
|
};
|
|
})
|
|
cfg.blocks;
|
|
|
|
capitalizedModid = lib.toUpper (lib.substring 0 1 cfg.modid) + lib.substring 1 (-1) cfg.modid;
|
|
|
|
templateContext = cfg // {
|
|
neoVersion = resolvedNeoVersion;
|
|
inherit (versionInfo) minecraftVersionRange loaderVersionRange parchmentMinecraftVersion parchmentMappingsVersion;
|
|
customClassName = classNameFromPath cfg.customClass;
|
|
customClientClassName = classNameFromPath cfg.customClientClass;
|
|
blocks = blocksList;
|
|
blocksClassName = "${capitalizedModid}Blocks";
|
|
blockEntitiesClassName = "${capitalizedModid}BlockEntities";
|
|
};
|
|
|
|
configJson = pkgs.writeText "snowy-mod-config-${cfg.modid}.json" (builtins.toJSON templateContext);
|
|
|
|
packagePath = builtins.replaceStrings [ "." ] [ "/" ] cfg.javaPackage;
|
|
|
|
pythonEnv = pkgs.python3.withPackages (ps: [ ps.jinja2 ]);
|
|
|
|
generated = pkgs.runCommand "snowy-generated-${cfg.modid}" {
|
|
nativeBuildInputs = [ pythonEnv ];
|
|
} ''
|
|
mkdir -p $out
|
|
python3 ${../codegen/main.py} \
|
|
--config ${configJson} \
|
|
--templates ${../templates}/${versionInfo.templateDir} \
|
|
--out $out
|
|
'';
|
|
|
|
# The `gradle wrapper` task validates its distribution URL over the network,
|
|
# which the (non-fixed-output) Nix build sandbox blocks. Instead, fetch the
|
|
# three wrapper artifacts verbatim (fixed-output derivations may use the
|
|
# network) from the upstream NeoForge MDK, which already pins Gradle 9.2.1,
|
|
# and write gradle-wrapper.properties ourselves.
|
|
gradleWrapperVersion = "9.2.1";
|
|
mdkWrapperBase = "https://raw.githubusercontent.com/NeoForgeMDKs/MDK-1.21.1-ModDevGradle/main";
|
|
|
|
gradlew = pkgs.fetchurl {
|
|
url = "${mdkWrapperBase}/gradlew";
|
|
sha256 = "0284687nawivy3gz16r4zcssv61a3g1ssvyxmxh5g7vl5v2v91xi";
|
|
};
|
|
gradlewBat = pkgs.fetchurl {
|
|
url = "${mdkWrapperBase}/gradlew.bat";
|
|
sha256 = "1kg2qjh904cz6da71l30pa1sfbdb707lr4lp4c1jvclgxc9jf44l";
|
|
};
|
|
gradleWrapperJar = pkgs.fetchurl {
|
|
url = "${mdkWrapperBase}/gradle/wrapper/gradle-wrapper.jar";
|
|
sha256 = "0wk157gn9nh2d7v9j7mfy46csb31iv5qxsx4qsdvachwvv24lfkx";
|
|
};
|
|
gradleWrapperProperties = pkgs.writeText "gradle-wrapper.properties" ''
|
|
distributionBase=GRADLE_USER_HOME
|
|
distributionPath=wrapper/dists
|
|
distributionUrl=https\://services.gradle.org/distributions/gradle-${gradleWrapperVersion}-bin.zip
|
|
networkTimeout=10000
|
|
validateDistributionUrl=true
|
|
zipStoreBase=GRADLE_USER_HOME
|
|
zipStorePath=wrapper/dists
|
|
'';
|
|
|
|
gradleWrapper = pkgs.runCommand "snowy-gradle-wrapper" { } ''
|
|
mkdir -p $out/gradle/wrapper
|
|
install -m755 ${gradlew} $out/gradlew
|
|
install -m755 ${gradlewBat} $out/gradlew.bat
|
|
install -m644 ${gradleWrapperJar} $out/gradle/wrapper/gradle-wrapper.jar
|
|
install -m644 ${gradleWrapperProperties} $out/gradle/wrapper/gradle-wrapper.properties
|
|
'';
|
|
in
|
|
assert lib.assertMsg (cfg.targetFramework == [ "neoforge" ])
|
|
"snowy: only targetFramework = [ \"neoforge\" ] is currently supported, got: [ ${lib.concatStringsSep " " cfg.targetFramework} ]";
|
|
pkgs.mkShell {
|
|
packages = [ pkgs.jdk21 pkgs.gradle ];
|
|
|
|
shellHook = ''
|
|
echo "snowy: refreshing generated NeoForge project files in $(pwd)"
|
|
|
|
# Only ever touch this explicit, framework-owned set of paths -- this is the
|
|
# real project root now (not an isolated throwaway directory), so it may also
|
|
# contain the mod author's own unrelated files (.git, flake.nix, an IDE config,
|
|
# hand-written sources outside src/main/java/**/generated, ...) that must never
|
|
# be swept up by this cleanup.
|
|
rm -rf build.gradle settings.gradle gradle.properties .gitignore \
|
|
gradlew gradlew.bat gradle \
|
|
src/main/resources/META-INF \
|
|
"src/main/resources/assets/${cfg.modid}/blockstates" \
|
|
"src/main/java/${packagePath}/generated"
|
|
|
|
cp -r --no-preserve=mode,ownership ${generated}/. ./
|
|
cp -r --no-preserve=mode,ownership ${gradleWrapper}/. ./
|
|
chmod +x gradlew
|
|
|
|
echo "snowy: done. ./gradlew build"
|
|
'';
|
|
}
|