factorio-experimental: 2.0.8 -> 2.0.9 (#350528)

This commit is contained in:
Luke Granger-Brown 2024-10-22 20:47:37 +01:00 committed by GitHub
commit dd38576ba9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 704 additions and 542 deletions

View File

@ -0,0 +1,310 @@
{
lib,
alsa-lib,
factorio-utils,
fetchurl,
libGL,
libICE,
libSM,
libX11,
libXcursor,
libXext,
libXi,
libXinerama,
libXrandr,
libpulseaudio,
libxkbcommon,
makeDesktopItem,
makeWrapper,
releaseType,
stdenv,
wayland,
mods-dat ? null,
versionsJson ? ./versions.json,
username ? "",
token ? "", # get/reset token at https://factorio.com/profile
experimental ? false, # true means to always use the latest branch
...
}@args:
assert
releaseType == "alpha"
|| releaseType == "headless"
|| releaseType == "demo"
|| releaseType == "expansion";
let
inherit (lib) importJSON;
mods = args.mods or [ ];
helpMsg = ''
===FETCH FAILED===
Please ensure you have set the username and token with config.nix, or
/etc/nix/nixpkgs-config.nix if on NixOS.
Your token can be seen at https://factorio.com/profile (after logging in). It is
not as sensitive as your password, but should still be safeguarded. There is a
link on that page to revoke/invalidate the token, if you believe it has been
leaked or wish to take precautions.
Example:
{
packageOverrides = pkgs: {
factorio = pkgs.factorio.override {
username = "FactorioPlayer1654";
token = "d5ad5a8971267c895c0da598688761";
};
};
}
Alternatively, instead of providing the username+token, you may manually
download the release through https://factorio.com/download , then add it to
the store using e.g.:
releaseType=alpha
version=0.17.74
nix-prefetch-url file://\''$HOME/Downloads/factorio_\''${releaseType}_x64_\''${version}.tar.xz --name factorio_\''${releaseType}_x64-\''${version}.tar.xz
Note the ultimate "_" is replaced with "-" in the --name arg!
'';
desktopItem = makeDesktopItem {
name = "factorio";
desktopName = "Factorio";
comment = "A game in which you build and maintain factories.";
exec = "factorio";
icon = "factorio";
categories = [ "Game" ];
};
branch = if experimental then "experimental" else "stable";
# NB `experimental` directs us to take the latest build, regardless of its branch;
# hence the (stable, experimental) pairs may sometimes refer to the same distributable.
versions = importJSON versionsJson;
binDists = makeBinDists versions;
actual =
binDists.${stdenv.hostPlatform.system}.${releaseType}.${branch}
or (throw "Factorio ${releaseType}-${branch} binaries for ${stdenv.hostPlatform.system} are not available for download.");
makeBinDists =
versions:
let
f =
path: name: value:
if builtins.isAttrs value then
if value ? "name" then makeBinDist value else builtins.mapAttrs (f (path ++ [ name ])) value
else
throw "expected attrset at ${toString path} - got ${toString value}";
in
builtins.mapAttrs (f [ ]) versions;
makeBinDist =
{
name,
version,
tarDirectory,
url,
sha256,
needsAuth,
candidateHashFilenames ? [ ],
}:
{
inherit version tarDirectory;
src =
if !needsAuth then
fetchurl { inherit name url sha256; }
else
(lib.overrideDerivation
(fetchurl {
inherit name url sha256;
curlOptsList = [
"--get"
"--data-urlencode"
"username@username"
"--data-urlencode"
"token@token"
];
})
(_: {
# This preHook hides the credentials from /proc
preHook =
if username != "" && token != "" then
''
echo -n "${username}" >username
echo -n "${token}" >token
''
else
''
# Deliberately failing since username/token was not provided, so we can't fetch.
# We can't use builtins.throw since we want the result to be used if the tar is in the store already.
exit 1
'';
failureHook = ''
cat <<EOF
${helpMsg}
EOF
'';
})
);
};
configBaseCfg = ''
use-system-read-write-data-directories=false
[path]
read-data=$out/share/factorio/data/
[other]
check_updates=false
'';
updateConfigSh = ''
#! $SHELL
if [[ -e ~/.factorio/config.cfg ]]; then
# Config file exists, but may have wrong path.
# Try to edit it. I'm sure this is perfectly safe and will never go wrong.
sed -i 's|^read-data=.*|read-data=$out/share/factorio/data/|' ~/.factorio/config.cfg
else
# Config file does not exist. Phew.
install -D $out/share/factorio/config-base.cfg ~/.factorio/config.cfg
fi
'';
modDir = factorio-utils.mkModDirDrv mods mods-dat;
base = with actual; {
# remap -expansion to -space-age to better match the attr name in nixpkgs.
pname = "factorio-${if releaseType == "expansion" then "space-age" else releaseType}";
inherit version src;
preferLocalBuild = true;
dontBuild = true;
installPhase = ''
mkdir -p $out/{bin,share/factorio}
cp -a data $out/share/factorio
cp -a bin/${tarDirectory}/factorio $out/bin/factorio
patchelf \
--set-interpreter $(cat $NIX_CC/nix-support/dynamic-linker) \
$out/bin/factorio
'';
passthru.updateScript =
if (username != "" && token != "") then
[
./update.py
"--username=${username}"
"--token=${token}"
]
else
null;
meta = {
description = "Game in which you build and maintain factories";
longDescription = ''
Factorio is a game in which you build and maintain factories.
You will be mining resources, researching technologies, building
infrastructure, automating production and fighting enemies. Use your
imagination to design your factory, combine simple elements into
ingenious structures, apply management skills to keep it working and
finally protect it from the creatures who don't really like you.
Factorio has been in development since spring of 2012, and reached
version 1.0 in mid 2020.
'';
homepage = "https://www.factorio.com/";
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
license = lib.licenses.unfree;
maintainers = with lib.maintainers; [
Baughn
elitak
priegger
lukegb
];
platforms = [ "x86_64-linux" ];
mainProgram = "factorio";
};
};
releases = rec {
headless = base;
demo = base // {
nativeBuildInputs = [ makeWrapper ];
buildInputs = [ libpulseaudio ];
libPath = lib.makeLibraryPath [
alsa-lib
libGL
libICE
libSM
libX11
libXcursor
libXext
libXi
libXinerama
libXrandr
libpulseaudio
libxkbcommon
wayland
];
installPhase =
base.installPhase
+ ''
wrapProgram $out/bin/factorio \
--prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \
--run "$out/share/factorio/update-config.sh" \
--argv0 "" \
--add-flags "-c \$HOME/.factorio/config.cfg" \
${lib.optionalString (mods != [ ]) "--add-flags --mod-directory=${modDir}"}
# TODO Currently, every time a mod is changed/added/removed using the
# modlist, a new derivation will take up the entire footprint of the
# client. The only way to avoid this is to remove the mods arg from the
# package function. The modsDir derivation will have to be built
# separately and have the user specify it in the .factorio config or
# right along side it using a symlink into the store I think i will
# just remove mods for the client derivation entirely. this is much
# cleaner and more useful for headless mode.
# TODO: trying to toggle off a mod will result in read-only-fs-error.
# not much we can do about that except warn the user somewhere. In
# fact, no exit will be clean, since this error will happen on close
# regardless. just prints an ugly stacktrace but seems to be otherwise
# harmless, unless maybe the user forgets and tries to use the mod
# manager.
install -m0644 <(cat << EOF
${configBaseCfg}
EOF
) $out/share/factorio/config-base.cfg
install -m0755 <(cat << EOF
${updateConfigSh}
EOF
) $out/share/factorio/update-config.sh
mkdir -p $out/share/icons/hicolor/{64x64,128x128}/apps
cp -a data/core/graphics/factorio-icon.png $out/share/icons/hicolor/64x64/apps/factorio.png
cp -a data/core/graphics/factorio-icon@2x.png $out/share/icons/hicolor/128x128/apps/factorio.png
ln -s ${desktopItem}/share/applications $out/share/
'';
};
alpha = demo // {
installPhase =
demo.installPhase
+ ''
cp -a doc-html $out/share/factorio
'';
};
expansion = alpha;
};
in
stdenv.mkDerivation (releases.${releaseType})

View File

@ -0,0 +1,289 @@
#!/usr/bin/env nix-shell
#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ ps.absl-py ps.requests ])"
from collections import defaultdict
import copy
from dataclasses import dataclass
import json
import os.path
from typing import Callable, Dict
from absl import app
from absl import flags
from absl import logging
import requests
FACTORIO_RELEASES = "https://factorio.com/api/latest-releases"
FACTORIO_HASHES = "https://factorio.com/download/sha256sums/"
FLAGS = flags.FLAGS
flags.DEFINE_string("out", "", "Output path for versions.json.")
flags.DEFINE_list(
"release_type",
"",
"If non-empty, a comma-separated list of release types to update (e.g. alpha).",
)
flags.DEFINE_list(
"release_channel",
"",
"If non-empty, a comma-separated list of release channels to update (e.g. experimental).",
)
@dataclass
class System:
nix_name: str
url_name: str
tar_name: str
@dataclass
class ReleaseType:
name: str
hash_filename_format: list[str]
needs_auth: bool = False
@dataclass
class ReleaseChannel:
name: str
FactorioVersionsJSON = Dict[str, Dict[str, str]]
OurVersionJSON = Dict[str, Dict[str, Dict[str, Dict[str, str]]]]
FactorioHashes = Dict[str, str]
SYSTEMS = [
System(nix_name="x86_64-linux", url_name="linux64", tar_name="x64"),
]
RELEASE_TYPES = [
ReleaseType(
"alpha",
needs_auth=True,
hash_filename_format=["factorio_linux_{version}.tar.xz"],
),
ReleaseType("demo", hash_filename_format=["factorio_demo_x64_{version}.tar.xz"]),
ReleaseType(
"headless",
hash_filename_format=[
"factorio-headless_linux_{version}.tar.xz",
"factorio_headless_x64_{version}.tar.xz",
],
),
ReleaseType(
"expansion",
needs_auth=True,
hash_filename_format=["factorio-space-age_linux_{version}.tar.xz"],
),
]
RELEASE_CHANNELS = [
ReleaseChannel("experimental"),
ReleaseChannel("stable"),
]
def find_versions_json() -> str:
if FLAGS.out:
return FLAGS.out
try_paths = ["pkgs/by-name/fa/factorio/versions.json", "versions.json"]
for path in try_paths:
if os.path.exists(path):
return path
raise Exception(
"Couldn't figure out where to write versions.json; try specifying --out"
)
def fetch_versions() -> FactorioVersionsJSON:
return json.loads(requests.get(FACTORIO_RELEASES).text)
def fetch_hashes() -> FactorioHashes:
resp = requests.get(FACTORIO_HASHES)
resp.raise_for_status()
out = {}
for ln in resp.text.split("\n"):
ln = ln.strip()
if not ln:
continue
sha256, filename = ln.split()
out[filename] = sha256
return out
def generate_our_versions(factorio_versions: FactorioVersionsJSON) -> OurVersionJSON:
def rec_dd():
return defaultdict(rec_dd)
output = rec_dd()
# Deal with times where there's no experimental version
for rc in RELEASE_CHANNELS:
if rc.name not in factorio_versions or not factorio_versions[rc.name]:
factorio_versions[rc.name] = factorio_versions["stable"]
for rt in RELEASE_TYPES:
if (
rt.name not in factorio_versions[rc.name]
or not factorio_versions[rc.name][rt.name]
):
factorio_versions[rc.name][rt.name] = factorio_versions["stable"][
rt.name
]
for system in SYSTEMS:
for release_type in RELEASE_TYPES:
for release_channel in RELEASE_CHANNELS:
version = factorio_versions[release_channel.name].get(release_type.name)
if version is None:
continue
this_release = {
"name": f"factorio_{release_type.name}_{system.tar_name}-{version}.tar.xz",
"url": f"https://factorio.com/get-download/{version}/{release_type.name}/{system.url_name}",
"version": version,
"needsAuth": release_type.needs_auth,
"candidateHashFilenames": [
fmt.format(version=version)
for fmt in release_type.hash_filename_format
],
"tarDirectory": system.tar_name,
}
output[system.nix_name][release_type.name][release_channel.name] = (
this_release
)
return output
def iter_version(
versions: OurVersionJSON,
it: Callable[[str, str, str, Dict[str, str]], Dict[str, str]],
) -> OurVersionJSON:
versions = copy.deepcopy(versions)
for system_name, system in versions.items():
for release_type_name, release_type in system.items():
for release_channel_name, release in release_type.items():
release_type[release_channel_name] = it(
system_name, release_type_name, release_channel_name, dict(release)
)
return versions
def merge_versions(old: OurVersionJSON, new: OurVersionJSON) -> OurVersionJSON:
"""Copies already-known hashes from version.json to avoid having to re-fetch."""
def _merge_version(
system_name: str,
release_type_name: str,
release_channel_name: str,
release: Dict[str, str],
) -> Dict[str, str]:
old_system = old.get(system_name, {})
old_release_type = old_system.get(release_type_name, {})
old_release = old_release_type.get(release_channel_name, {})
if FLAGS.release_type and release_type_name not in FLAGS.release_type:
logging.info(
"%s/%s/%s: not in --release_type, not updating",
system_name,
release_type_name,
release_channel_name,
)
return old_release
if FLAGS.release_channel and release_channel_name not in FLAGS.release_channel:
logging.info(
"%s/%s/%s: not in --release_channel, not updating",
system_name,
release_type_name,
release_channel_name,
)
return old_release
if "sha256" not in old_release:
logging.info(
"%s/%s/%s: not copying sha256 since it's missing",
system_name,
release_type_name,
release_channel_name,
)
return release
if not all(
old_release.get(k, None) == release[k] for k in ["name", "version", "url"]
):
logging.info(
"%s/%s/%s: not copying sha256 due to mismatch",
system_name,
release_type_name,
release_channel_name,
)
return release
release["sha256"] = old_release["sha256"]
return release
return iter_version(new, _merge_version)
def fill_in_hash(
versions: OurVersionJSON, factorio_hashes: FactorioHashes
) -> OurVersionJSON:
"""Fill in sha256 hashes for anything missing them."""
def _fill_in_hash(
system_name: str,
release_type_name: str,
release_channel_name: str,
release: Dict[str, str],
) -> Dict[str, str]:
for candidate_filename in release["candidateHashFilenames"]:
if candidate_filename in factorio_hashes:
release["sha256"] = factorio_hashes[candidate_filename]
break
else:
logging.error(
"%s/%s/%s: failed to find any of %s in %s",
system_name,
release_type_name,
release_channel_name,
release["candidateHashFilenames"],
FACTORIO_HASHES,
)
return release
if "sha256" in release:
logging.info(
"%s/%s/%s: skipping fetch, sha256 already present",
system_name,
release_type_name,
release_channel_name,
)
return release
return release
return iter_version(versions, _fill_in_hash)
def main(argv):
factorio_versions = fetch_versions()
factorio_hashes = fetch_hashes()
new_our_versions = generate_our_versions(factorio_versions)
old_our_versions = None
our_versions_path = find_versions_json()
if our_versions_path:
logging.info("Loading old versions.json from %s", our_versions_path)
with open(our_versions_path, "r") as f:
old_our_versions = json.load(f)
if old_our_versions:
logging.info("Merging in old hashes")
new_our_versions = merge_versions(old_our_versions, new_our_versions)
logging.info("Updating hashes from Factorio SHA256")
new_our_versions = fill_in_hash(new_our_versions, factorio_hashes)
with open(our_versions_path, "w") as f:
logging.info("Writing versions.json to %s", our_versions_path)
json.dump(new_our_versions, f, sort_keys=True, indent=2)
f.write("\n")
if __name__ == "__main__":
app.run(main)

View File

@ -0,0 +1,102 @@
{
"x86_64-linux": {
"alpha": {
"experimental": {
"candidateHashFilenames": [
"factorio_linux_2.0.9.tar.xz"
],
"name": "factorio_alpha_x64-2.0.9.tar.xz",
"needsAuth": true,
"sha256": "34c21cd3cbe91b65483786ccb4467b5d4766c748cbbddd2ce3b30d319d163e3b",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.9/alpha/linux64",
"version": "2.0.9"
},
"stable": {
"candidateHashFilenames": [
"factorio_linux_2.0.8.tar.xz"
],
"name": "factorio_alpha_x64-2.0.8.tar.xz",
"needsAuth": true,
"sha256": "94ea36a5b9103369df7158a8281039dd2f1d7fa7bb3a2d854c715250dd73e185",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/alpha/linux64",
"version": "2.0.8"
}
},
"demo": {
"experimental": {
"candidateHashFilenames": [
"factorio_demo_x64_1.1.110.tar.xz"
],
"name": "factorio_demo_x64-1.1.110.tar.xz",
"needsAuth": false,
"sha256": "bddb91dcba9f300c25d590f861772eaf41f0b6ce8ae6b754de00d0e5f3eb5a35",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/1.1.110/demo/linux64",
"version": "1.1.110"
},
"stable": {
"candidateHashFilenames": [
"factorio_demo_x64_1.1.110.tar.xz"
],
"name": "factorio_demo_x64-1.1.110.tar.xz",
"needsAuth": false,
"sha256": "bddb91dcba9f300c25d590f861772eaf41f0b6ce8ae6b754de00d0e5f3eb5a35",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/1.1.110/demo/linux64",
"version": "1.1.110"
}
},
"expansion": {
"experimental": {
"candidateHashFilenames": [
"factorio-space-age_linux_2.0.9.tar.xz"
],
"name": "factorio_expansion_x64-2.0.9.tar.xz",
"needsAuth": true,
"sha256": "6369d23550a7a721d3de1d34253e8321ee601fa759d1fb5efac9abc28aa7509d",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.9/expansion/linux64",
"version": "2.0.9"
},
"stable": {
"candidateHashFilenames": [
"factorio-space-age_linux_2.0.8.tar.xz"
],
"name": "factorio_expansion_x64-2.0.8.tar.xz",
"needsAuth": true,
"sha256": "408eae824daa761564b1ea7b81925efe05298cbaffd120eea235341ac05a6a60",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/expansion/linux64",
"version": "2.0.8"
}
},
"headless": {
"experimental": {
"candidateHashFilenames": [
"factorio-headless_linux_2.0.9.tar.xz",
"factorio_headless_x64_2.0.9.tar.xz"
],
"name": "factorio_headless_x64-2.0.9.tar.xz",
"needsAuth": false,
"sha256": "f499077b3e2c1313452c350f1faf17db31cae2a0fa738f69166e97c3caa3c86d",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.9/headless/linux64",
"version": "2.0.9"
},
"stable": {
"candidateHashFilenames": [
"factorio-headless_linux_2.0.8.tar.xz",
"factorio_headless_x64_2.0.8.tar.xz"
],
"name": "factorio_headless_x64-2.0.8.tar.xz",
"needsAuth": false,
"sha256": "d9594c4d552a3e4f965b188a4774da8c8b010fc23ddb0efc63b1d94818dde1ca",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/headless/linux64",
"version": "2.0.8"
}
}
}
}

View File

@ -1,280 +0,0 @@
{ lib
, alsa-lib
, factorio-utils
, fetchurl
, libGL
, libICE
, libSM
, libX11
, libXcursor
, libXext
, libXi
, libXinerama
, libXrandr
, libpulseaudio
, libxkbcommon
, makeDesktopItem
, makeWrapper
, releaseType
, stdenv
, wayland
, mods-dat ? null
, versionsJson ? ./versions.json
, username ? ""
, token ? "" # get/reset token at https://factorio.com/profile
, experimental ? false # true means to always use the latest branch
, ...
} @ args:
assert releaseType == "alpha"
|| releaseType == "headless"
|| releaseType == "demo"
|| releaseType == "expansion";
let
inherit (lib) importJSON;
mods = args.mods or [ ];
helpMsg = ''
===FETCH FAILED===
Please ensure you have set the username and token with config.nix, or
/etc/nix/nixpkgs-config.nix if on NixOS.
Your token can be seen at https://factorio.com/profile (after logging in). It is
not as sensitive as your password, but should still be safeguarded. There is a
link on that page to revoke/invalidate the token, if you believe it has been
leaked or wish to take precautions.
Example:
{
packageOverrides = pkgs: {
factorio = pkgs.factorio.override {
username = "FactorioPlayer1654";
token = "d5ad5a8971267c895c0da598688761";
};
};
}
Alternatively, instead of providing the username+token, you may manually
download the release through https://factorio.com/download , then add it to
the store using e.g.:
releaseType=alpha
version=0.17.74
nix-prefetch-url file://\''$HOME/Downloads/factorio_\''${releaseType}_x64_\''${version}.tar.xz --name factorio_\''${releaseType}_x64-\''${version}.tar.xz
Note the ultimate "_" is replaced with "-" in the --name arg!
'';
desktopItem = makeDesktopItem {
name = "factorio";
desktopName = "Factorio";
comment = "A game in which you build and maintain factories.";
exec = "factorio";
icon = "factorio";
categories = [ "Game" ];
};
branch = if experimental then "experimental" else "stable";
# NB `experimental` directs us to take the latest build, regardless of its branch;
# hence the (stable, experimental) pairs may sometimes refer to the same distributable.
versions = importJSON versionsJson;
binDists = makeBinDists versions;
actual = binDists.${stdenv.hostPlatform.system}.${releaseType}.${branch} or (throw "Factorio ${releaseType}-${branch} binaries for ${stdenv.hostPlatform.system} are not available for download.");
makeBinDists = versions:
let
f = path: name: value:
if builtins.isAttrs value then
if value ? "name" then
makeBinDist value
else
builtins.mapAttrs (f (path ++ [ name ])) value
else
throw "expected attrset at ${toString path} - got ${toString value}";
in
builtins.mapAttrs (f [ ]) versions;
makeBinDist = { name, version, tarDirectory, url, sha256, needsAuth }: {
inherit version tarDirectory;
src =
if !needsAuth then
fetchurl { inherit name url sha256; }
else
(lib.overrideDerivation
(fetchurl {
inherit name url sha256;
curlOptsList = [
"--get"
"--data-urlencode"
"username@username"
"--data-urlencode"
"token@token"
];
})
(_: {
# This preHook hides the credentials from /proc
preHook =
if username != "" && token != "" then ''
echo -n "${username}" >username
echo -n "${token}" >token
'' else ''
# Deliberately failing since username/token was not provided, so we can't fetch.
# We can't use builtins.throw since we want the result to be used if the tar is in the store already.
exit 1
'';
failureHook = ''
cat <<EOF
${helpMsg}
EOF
'';
}));
};
configBaseCfg = ''
use-system-read-write-data-directories=false
[path]
read-data=$out/share/factorio/data/
[other]
check_updates=false
'';
updateConfigSh = ''
#! $SHELL
if [[ -e ~/.factorio/config.cfg ]]; then
# Config file exists, but may have wrong path.
# Try to edit it. I'm sure this is perfectly safe and will never go wrong.
sed -i 's|^read-data=.*|read-data=$out/share/factorio/data/|' ~/.factorio/config.cfg
else
# Config file does not exist. Phew.
install -D $out/share/factorio/config-base.cfg ~/.factorio/config.cfg
fi
'';
modDir = factorio-utils.mkModDirDrv mods mods-dat;
base = with actual; {
pname = "factorio-${releaseType}";
inherit version src;
preferLocalBuild = true;
dontBuild = true;
installPhase = ''
mkdir -p $out/{bin,share/factorio}
cp -a data $out/share/factorio
cp -a bin/${tarDirectory}/factorio $out/bin/factorio
patchelf \
--set-interpreter $(cat $NIX_CC/nix-support/dynamic-linker) \
$out/bin/factorio
'';
passthru.updateScript =
if (username != "" && token != "") then [
./update.py
"--username=${username}"
"--token=${token}"
] else null;
meta = {
description = "Game in which you build and maintain factories";
longDescription = ''
Factorio is a game in which you build and maintain factories.
You will be mining resources, researching technologies, building
infrastructure, automating production and fighting enemies. Use your
imagination to design your factory, combine simple elements into
ingenious structures, apply management skills to keep it working and
finally protect it from the creatures who don't really like you.
Factorio has been in development since spring of 2012, and reached
version 1.0 in mid 2020.
'';
homepage = "https://www.factorio.com/";
sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
license = lib.licenses.unfree;
maintainers = with lib.maintainers; [ Baughn elitak priegger lukegb ];
platforms = [ "x86_64-linux" ];
mainProgram = "factorio";
};
};
releases = rec {
headless = base;
demo = base // {
nativeBuildInputs = [ makeWrapper ];
buildInputs = [ libpulseaudio ];
libPath = lib.makeLibraryPath [
alsa-lib
libGL
libICE
libSM
libX11
libXcursor
libXext
libXi
libXinerama
libXrandr
libpulseaudio
libxkbcommon
wayland
];
installPhase = base.installPhase + ''
wrapProgram $out/bin/factorio \
--prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \
--run "$out/share/factorio/update-config.sh" \
--argv0 "" \
--add-flags "-c \$HOME/.factorio/config.cfg" \
${lib.optionalString (mods!=[]) "--add-flags --mod-directory=${modDir}"}
# TODO Currently, every time a mod is changed/added/removed using the
# modlist, a new derivation will take up the entire footprint of the
# client. The only way to avoid this is to remove the mods arg from the
# package function. The modsDir derivation will have to be built
# separately and have the user specify it in the .factorio config or
# right along side it using a symlink into the store I think i will
# just remove mods for the client derivation entirely. this is much
# cleaner and more useful for headless mode.
# TODO: trying to toggle off a mod will result in read-only-fs-error.
# not much we can do about that except warn the user somewhere. In
# fact, no exit will be clean, since this error will happen on close
# regardless. just prints an ugly stacktrace but seems to be otherwise
# harmless, unless maybe the user forgets and tries to use the mod
# manager.
install -m0644 <(cat << EOF
${configBaseCfg}
EOF
) $out/share/factorio/config-base.cfg
install -m0755 <(cat << EOF
${updateConfigSh}
EOF
) $out/share/factorio/update-config.sh
mkdir -p $out/share/icons/hicolor/{64x64,128x128}/apps
cp -a data/core/graphics/factorio-icon.png $out/share/icons/hicolor/64x64/apps/factorio.png
cp -a data/core/graphics/factorio-icon@2x.png $out/share/icons/hicolor/128x128/apps/factorio.png
ln -s ${desktopItem}/share/applications $out/share/
'';
};
alpha = demo // {
installPhase = demo.installPhase + ''
cp -a doc-html $out/share/factorio
'';
};
expansion = alpha;
};
in
stdenv.mkDerivation (releases.${releaseType})

View File

@ -1,191 +0,0 @@
#!/usr/bin/env nix-shell
#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ ps.absl-py ps.requests ])" nix
from collections import defaultdict
import copy
from dataclasses import dataclass
import json
import os.path
import subprocess
from typing import Callable, Dict
from absl import app
from absl import flags
from absl import logging
import requests
FACTORIO_API = "https://factorio.com/api/latest-releases"
FLAGS = flags.FLAGS
flags.DEFINE_string('username', '', 'Factorio username for retrieving binaries.')
flags.DEFINE_string('token', '', 'Factorio token for retrieving binaries.')
flags.DEFINE_string('out', '', 'Output path for versions.json.')
flags.DEFINE_list('release_type', '', 'If non-empty, a comma-separated list of release types to update (e.g. alpha).')
flags.DEFINE_list('release_channel', '', 'If non-empty, a comma-separated list of release channels to update (e.g. experimental).')
@dataclass
class System:
nix_name: str
url_name: str
tar_name: str
@dataclass
class ReleaseType:
name: str
needs_auth: bool = False
@dataclass
class ReleaseChannel:
name: str
FactorioVersionsJSON = Dict[str, Dict[str, str]]
OurVersionJSON = Dict[str, Dict[str, Dict[str, Dict[str, str]]]]
SYSTEMS = [
System(nix_name="x86_64-linux", url_name="linux64", tar_name="x64"),
]
RELEASE_TYPES = [
ReleaseType("alpha", needs_auth=True),
ReleaseType("expansion", needs_auth=True),
ReleaseType("demo"),
ReleaseType("headless"),
]
RELEASE_CHANNELS = [
ReleaseChannel("experimental"),
ReleaseChannel("stable"),
]
def find_versions_json() -> str:
if FLAGS.out:
return FLAGS.out
try_paths = ["pkgs/games/factorio/versions.json", "versions.json"]
for path in try_paths:
if os.path.exists(path):
return path
raise Exception("Couldn't figure out where to write versions.json; try specifying --out")
def fetch_versions() -> FactorioVersionsJSON:
return json.loads(requests.get("https://factorio.com/api/latest-releases").text)
def generate_our_versions(factorio_versions: FactorioVersionsJSON) -> OurVersionJSON:
rec_dd = lambda: defaultdict(rec_dd)
output = rec_dd()
# Deal with times where there's no experimental version
for rc in RELEASE_CHANNELS:
if not factorio_versions[rc.name]:
factorio_versions[rc.name] = factorio_versions['stable']
for system in SYSTEMS:
for release_type in RELEASE_TYPES:
for release_channel in RELEASE_CHANNELS:
version = factorio_versions[release_channel.name].get(release_type.name)
if version == None:
continue
this_release = {
"name": f"factorio_{release_type.name}_{system.tar_name}-{version}.tar.xz",
"url": f"https://factorio.com/get-download/{version}/{release_type.name}/{system.url_name}",
"version": version,
"needsAuth": release_type.needs_auth,
"tarDirectory": system.tar_name,
}
output[system.nix_name][release_type.name][release_channel.name] = this_release
return output
def iter_version(versions: OurVersionJSON, it: Callable[[str, str, str, Dict[str, str]], Dict[str, str]]) -> OurVersionJSON:
versions = copy.deepcopy(versions)
for system_name, system in versions.items():
for release_type_name, release_type in system.items():
for release_channel_name, release in release_type.items():
release_type[release_channel_name] = it(system_name, release_type_name, release_channel_name, dict(release))
return versions
def merge_versions(old: OurVersionJSON, new: OurVersionJSON) -> OurVersionJSON:
"""Copies already-known hashes from version.json to avoid having to re-fetch."""
def _merge_version(system_name: str, release_type_name: str, release_channel_name: str, release: Dict[str, str]) -> Dict[str, str]:
old_system = old.get(system_name, {})
old_release_type = old_system.get(release_type_name, {})
old_release = old_release_type.get(release_channel_name, {})
if FLAGS.release_type and release_type_name not in FLAGS.release_type:
logging.info("%s/%s/%s: not in --release_type, not updating", system_name, release_type_name, release_channel_name)
return old_release
if FLAGS.release_channel and release_channel_name not in FLAGS.release_channel:
logging.info("%s/%s/%s: not in --release_channel, not updating", system_name, release_type_name, release_channel_name)
return old_release
if not "sha256" in old_release:
logging.info("%s/%s/%s: not copying sha256 since it's missing", system_name, release_type_name, release_channel_name)
return release
if not all(old_release.get(k, None) == release[k] for k in ['name', 'version', 'url']):
logging.info("%s/%s/%s: not copying sha256 due to mismatch", system_name, release_type_name, release_channel_name)
return release
release["sha256"] = old_release["sha256"]
return release
return iter_version(new, _merge_version)
def nix_prefetch_url(name: str, url: str, algo: str = 'sha256') -> str:
cmd = ['nix-prefetch-url', '--type', algo, '--name', name, url]
logging.info('running %s', cmd)
out = subprocess.check_output(cmd)
return out.decode('utf-8').strip()
def fill_in_hash(versions: OurVersionJSON) -> OurVersionJSON:
"""Fill in sha256 hashes for anything missing them."""
urls_to_hash = {}
def _fill_in_hash(system_name: str, release_type_name: str, release_channel_name: str, release: Dict[str, str]) -> Dict[str, str]:
if "sha256" in release:
logging.info("%s/%s/%s: skipping fetch, sha256 already present", system_name, release_type_name, release_channel_name)
return release
url = release["url"]
if url in urls_to_hash:
logging.info("%s/%s/%s: found url %s in cache", system_name, release_type_name, release_channel_name, url)
release["sha256"] = urls_to_hash[url]
return release
logging.info("%s/%s/%s: fetching %s", system_name, release_type_name, release_channel_name, url)
if release["needsAuth"]:
if not FLAGS.username or not FLAGS.token:
raise Exception("fetching %s/%s/%s from %s requires --username and --token" % (system_name, release_type_name, release_channel_name, url))
url += f"?username={FLAGS.username}&token={FLAGS.token}"
release["sha256"] = nix_prefetch_url(release["name"], url)
urls_to_hash[url] = release["sha256"]
return release
return iter_version(versions, _fill_in_hash)
def main(argv):
factorio_versions = fetch_versions()
new_our_versions = generate_our_versions(factorio_versions)
old_our_versions = None
our_versions_path = find_versions_json()
if our_versions_path:
logging.info('Loading old versions.json from %s', our_versions_path)
with open(our_versions_path, 'r') as f:
old_our_versions = json.load(f)
if old_our_versions:
logging.info('Merging in old hashes')
new_our_versions = merge_versions(old_our_versions, new_our_versions)
logging.info('Fetching necessary tars to get hashes')
new_our_versions = fill_in_hash(new_our_versions)
with open(our_versions_path, 'w') as f:
logging.info('Writing versions.json to %s', our_versions_path)
json.dump(new_our_versions, f, sort_keys=True, indent=2)
f.write("\n")
if __name__ == '__main__':
app.run(main)

View File

@ -1,68 +0,0 @@
{
"x86_64-linux": {
"alpha": {
"experimental": {
"name": "factorio_alpha_x64-2.0.8.tar.xz",
"needsAuth": true,
"sha256": "11g1fgfm0lki9j2jsfmvlxzisbyx7482ia2qf7gnjcqhp6jkdsll",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/alpha/linux64",
"version": "2.0.8"
},
"stable": {
"name": "factorio_alpha_x64-2.0.8.tar.xz",
"needsAuth": true,
"sha256": "11g1fgfm0lki9j2jsfmvlxzisbyx7482ia2qf7gnjcqhp6jkdsll",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/alpha/linux64",
"version": "2.0.8"
}
},
"demo": {
"experimental": {
"name": "factorio_demo_x64-1.1.110.tar.xz",
"needsAuth": false,
"sha256": "0dasxgrybl00vrabgrlarsvg0hdg5rvn3y4hsljhqc4zpbf93nxx",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/1.1.110/demo/linux64",
"version": "1.1.110"
},
"stable": {
"name": "factorio_demo_x64-1.1.110.tar.xz",
"needsAuth": false,
"sha256": "0dasxgrybl00vrabgrlarsvg0hdg5rvn3y4hsljhqc4zpbf93nxx",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/1.1.110/demo/linux64",
"version": "1.1.110"
}
},
"expansion": {
"stable": {
"name": "factorio_expansion_x64-2.0.8.tar.xz",
"needsAuth": true,
"sha256": "0q3abb01ld1mlbp21lgzpa62j1gybs982yzan5j1axma9n1ax3j0",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/expansion/linux64",
"version": "2.0.8"
}
},
"headless": {
"experimental": {
"name": "factorio_headless_x64-2.0.8.tar.xz",
"needsAuth": false,
"sha256": "1jp1vlc4indicgy0xnrxq87h32wcv9s4g2hqbfb4ygiaam6lqnfr",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/headless/linux64",
"version": "2.0.8"
},
"stable": {
"name": "factorio_headless_x64-2.0.8.tar.xz",
"needsAuth": false,
"sha256": "1jp1vlc4indicgy0xnrxq87h32wcv9s4g2hqbfb4ygiaam6lqnfr",
"tarDirectory": "x64",
"url": "https://factorio.com/get-download/2.0.8/headless/linux64",
"version": "2.0.8"
}
}
}
}

View File

@ -34534,7 +34534,7 @@ with pkgs;
fltk = fltk-minimal;
};
factorio = callPackage ../games/factorio { releaseType = "alpha"; };
factorio = callPackage ../by-name/fa/factorio/package.nix { releaseType = "alpha"; };
factorio-experimental = factorio.override { releaseType = "alpha"; experimental = true; };
@ -34548,9 +34548,9 @@ with pkgs;
factorio-space-age-experimental = factorio.override { releaseType = "expansion"; experimental = true; };
factorio-mods = callPackage ../games/factorio/mods.nix { };
factorio-mods = callPackage ../by-name/fa/factorio/mods.nix { };
factorio-utils = callPackage ../games/factorio/utils.nix { };
factorio-utils = callPackage ../by-name/fa/factorio/utils.nix { };
fairymax = callPackage ../games/fairymax { };