Unverified Commit dd38576b authored by Luke Granger-Brown's avatar Luke Granger-Brown Committed by GitHub
Browse files

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

parents 0ea7534a 0d3fab39
Loading
Loading
Loading
Loading
+310 −0
Original line number Diff line number Diff line
{ 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
, ...
{
  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"
assert
  releaseType == "alpha"
  || releaseType == "headless"
  || releaseType == "demo"
  || releaseType == "expansion";
@@ -86,21 +88,32 @@ let
  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.");
  actual =
    binDists.${stdenv.hostPlatform.system}.${releaseType}.${branch}
      or (throw "Factorio ${releaseType}-${branch} binaries for ${stdenv.hostPlatform.system} are not available for download.");

  makeBinDists = versions:
  makeBinDists =
    versions:
    let
      f = path: name: value:
      f =
        path: name: value:
        if builtins.isAttrs value then
          if value ? "name" then
            makeBinDist value
          else
            builtins.mapAttrs (f (path ++ [ name ])) value
          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 }: {
  makeBinDist =
    {
      name,
      version,
      tarDirectory,
      url,
      sha256,
      needsAuth,
      candidateHashFilenames ? [ ],
    }:
    {
      inherit version tarDirectory;
      src =
        if !needsAuth then
@@ -120,10 +133,13 @@ let
            (_: {
              # This preHook hides the credentials from /proc
              preHook =
              if username != "" && token != "" then ''
                if username != "" && token != "" then
                  ''
                    echo -n "${username}" >username
                    echo -n "${token}"    >token
              '' else ''
                  ''
                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
@@ -133,7 +149,8 @@ let
                ${helpMsg}
                EOF
              '';
          }));
            })
          );
    };

  configBaseCfg = ''
@@ -159,7 +176,8 @@ let
  modDir = factorio-utils.mkModDirDrv mods mods-dat;

  base = with actual; {
    pname = "factorio-${releaseType}";
    # 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;
@@ -175,11 +193,14 @@ let
    '';

    passthru.updateScript =
      if (username != "" && token != "") then [
      if (username != "" && token != "") then
        [
          ./update.py
          "--username=${username}"
          "--token=${token}"
      ] else null;
        ]
      else
        null;

    meta = {
      description = "Game in which you build and maintain factories";
@@ -198,7 +219,12 @@ let
      homepage = "https://www.factorio.com/";
      sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
      license = lib.licenses.unfree;
      maintainers = with lib.maintainers; [ Baughn elitak priegger lukegb ];
      maintainers = with lib.maintainers; [
        Baughn
        elitak
        priegger
        lukegb
      ];
      platforms = [ "x86_64-linux" ];
      mainProgram = "factorio";
    };
@@ -227,7 +253,9 @@ let
        wayland
      ];

      installPhase = base.installPhase + ''
      installPhase =
        base.installPhase
        + ''
          wrapProgram $out/bin/factorio                                \
            --prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \
            --run "$out/share/factorio/update-config.sh"               \
@@ -269,7 +297,9 @@ let
    };
    alpha = demo // {

      installPhase = demo.installPhase + ''
      installPhase =
        demo.installPhase
        + ''
          cp -a doc-html $out/share/factorio
        '';
    };
+289 −0
Original line number Diff line number Diff line
#!/usr/bin/env nix-shell
#! nix-shell -i python -p "python3.withPackages (ps: with ps; [ ps.absl-py ps.requests ])" nix
#! 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
import subprocess
from typing import Callable, Dict

from absl import app
@@ -15,16 +14,23 @@ from absl import logging
import requests


FACTORIO_API = "https://factorio.com/api/latest-releases"
FACTORIO_RELEASES = "https://factorio.com/api/latest-releases"
FACTORIO_HASHES = "https://factorio.com/download/sha256sums/"


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).')
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
@@ -37,6 +43,7 @@ class System:
@dataclass
class ReleaseType:
    name: str
    hash_filename_format: list[str]
    needs_auth: bool = False


@@ -48,16 +55,32 @@ class ReleaseChannel:
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),
    ReleaseType("expansion", needs_auth=True),
    ReleaseType("demo"),
    ReleaseType("headless"),
    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 = [
@@ -69,123 +92,198 @@ RELEASE_CHANNELS = [
def find_versions_json() -> str:
    if FLAGS.out:
        return FLAGS.out
    try_paths = ["pkgs/games/factorio/versions.json", "versions.json"]
    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")
    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)
    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:
    rec_dd = lambda: defaultdict(rec_dd)
    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 not factorio_versions[rc.name]:
            factorio_versions[rc.name] = factorio_versions['stable']
        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 == None:
                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
                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:
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))
                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]:

    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)
            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)
            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)
        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)
        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()
    return iter_version(new, _merge_version)


def fill_in_hash(versions: OurVersionJSON) -> OurVersionJSON:
def fill_in_hash(
    versions: OurVersionJSON, factorio_hashes: FactorioHashes
) -> 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)

    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
        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]
        if "sha256" in release:
            logging.info(
                "%s/%s/%s: skipping fetch, sha256 already present",
                system_name,
                release_type_name,
                release_channel_name,
            )
            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()
    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:
        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')
        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)
    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__':

if __name__ == "__main__":
    app.run(main)
+102 −0
Original line number Diff line number Diff line
@@ -2,17 +2,23 @@
  "x86_64-linux": {
    "alpha": {
      "experimental": {
        "name": "factorio_alpha_x64-2.0.8.tar.xz",
        "candidateHashFilenames": [
          "factorio_linux_2.0.9.tar.xz"
        ],
        "name": "factorio_alpha_x64-2.0.9.tar.xz",
        "needsAuth": true,
        "sha256": "11g1fgfm0lki9j2jsfmvlxzisbyx7482ia2qf7gnjcqhp6jkdsll",
        "sha256": "34c21cd3cbe91b65483786ccb4467b5d4766c748cbbddd2ce3b30d319d163e3b",
        "tarDirectory": "x64",
        "url": "https://factorio.com/get-download/2.0.8/alpha/linux64",
        "version": "2.0.8"
        "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": "11g1fgfm0lki9j2jsfmvlxzisbyx7482ia2qf7gnjcqhp6jkdsll",
        "sha256": "94ea36a5b9103369df7158a8281039dd2f1d7fa7bb3a2d854c715250dd73e185",
        "tarDirectory": "x64",
        "url": "https://factorio.com/get-download/2.0.8/alpha/linux64",
        "version": "2.0.8"
@@ -20,27 +26,47 @@
    },
    "demo": {
      "experimental": {
        "candidateHashFilenames": [
          "factorio_demo_x64_1.1.110.tar.xz"
        ],
        "name": "factorio_demo_x64-1.1.110.tar.xz",
        "needsAuth": false,
        "sha256": "0dasxgrybl00vrabgrlarsvg0hdg5rvn3y4hsljhqc4zpbf93nxx",
        "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": "0dasxgrybl00vrabgrlarsvg0hdg5rvn3y4hsljhqc4zpbf93nxx",
        "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": "0q3abb01ld1mlbp21lgzpa62j1gybs982yzan5j1axma9n1ax3j0",
        "sha256": "408eae824daa761564b1ea7b81925efe05298cbaffd120eea235341ac05a6a60",
        "tarDirectory": "x64",
        "url": "https://factorio.com/get-download/2.0.8/expansion/linux64",
        "version": "2.0.8"
@@ -48,17 +74,25 @@
    },
    "headless": {
      "experimental": {
        "name": "factorio_headless_x64-2.0.8.tar.xz",
        "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": "1jp1vlc4indicgy0xnrxq87h32wcv9s4g2hqbfb4ygiaam6lqnfr",
        "sha256": "f499077b3e2c1313452c350f1faf17db31cae2a0fa738f69166e97c3caa3c86d",
        "tarDirectory": "x64",
        "url": "https://factorio.com/get-download/2.0.8/headless/linux64",
        "version": "2.0.8"
        "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": "1jp1vlc4indicgy0xnrxq87h32wcv9s4g2hqbfb4ygiaam6lqnfr",
        "sha256": "d9594c4d552a3e4f965b188a4774da8c8b010fc23ddb0efc63b1d94818dde1ca",
        "tarDirectory": "x64",
        "url": "https://factorio.com/get-download/2.0.8/headless/linux64",
        "version": "2.0.8"
Loading