From 2c8f7755d40dd1a77f99d766e1d23a3d4ddf67a1 Mon Sep 17 00:00:00 2001 From: Luke Granger-Brown Date: Sat, 28 Nov 2020 22:52:07 +0000 Subject: [PATCH] factorio: add an updateScript The updateScript knows how to automatically fetch and update the version data from the Factorio versions API (and update the hashes appropriately), which makes it easier to update whenever experimental does. --- pkgs/games/factorio/default.nix | 120 ++++++++++----------- pkgs/games/factorio/update.py | 174 ++++++++++++++++++++++++++++++ pkgs/games/factorio/versions.json | 58 ++++++++++ 3 files changed, 286 insertions(+), 66 deletions(-) create mode 100755 pkgs/games/factorio/update.py create mode 100644 pkgs/games/factorio/versions.json diff --git a/pkgs/games/factorio/default.nix b/pkgs/games/factorio/default.nix index f5f12b47dd7..14e00380e87 100644 --- a/pkgs/games/factorio/default.nix +++ b/pkgs/games/factorio/default.nix @@ -13,6 +13,8 @@ assert releaseType == "alpha" let + inherit (stdenv.lib) importJSON; + helpMsg = '' ===FETCH FAILED=== @@ -59,72 +61,54 @@ let # 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. - binDists = { - x86_64-linux = let bdist = bdistForArch { inUrl = "linux64"; inTar = "x64"; }; in { - alpha = { - stable = bdist { sha256 = "0zixscff0svpb0yg8nzczp2z4filqqxi1k0z0nrpzn2hhzhf1464"; version = "1.0.0"; withAuth = true; }; - experimental = bdist { sha256 = "0cmia16d5dhy3f8mck926d7rrnavxmvb6a72ymjllxm37slsx60j"; version = "1.1.2"; withAuth = true; }; - }; - headless = { - stable = bdist { sha256 = "0r0lplns8nxna2viv8qyx9mp4cckdvx6k20w2g2fwnj3jjmf3nc1"; version = "1.0.0"; }; - experimental = bdist { sha256 = "0x3lwz11z8cczqr5i799m4yg8x3yk6h5qz48pfzw4l2ikrrwgahd"; version = "1.1.2"; }; - }; - demo = { - stable = bdist { sha256 = "0h9cqbp143w47zcl4qg4skns4cngq0k40s5jwbk0wi5asjz8whqn"; version = "1.0.0"; }; - }; - }; - i686-linux = let bdist = bdistForArch { inUrl = "linux32"; inTar = "i386"; }; in { - alpha = { - stable = bdist { sha256 = "0nnfkxxqnywx1z05xnndgh71gp4izmwdk026nnjih74m2k5j086l"; version = "0.14.23"; withAuth = true; nameMut = asGz; }; - }; - }; - }; + versions = importJSON ./versions.json; + 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."); - bdistForArch = arch: { version - , sha256 - , withAuth ? false - , nameMut ? x: x - }: - let - url = "https://factorio.com/get-download/${version}/${releaseType}/${arch.inUrl}"; - name = nameMut "factorio_${releaseType}_${arch.inTar}-${version}.tar.xz"; - in { - inherit version arch; - src = - if withAuth then - (stdenv.lib.overrideDerivation - (fetchurl { - inherit name url sha256; - curlOpts = [ - "--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 <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 < str: + if FLAGS.out: + return 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() + for system in SYSTEMS: + for release_type in RELEASE_TYPES: + for release_channel in RELEASE_CHANNELS: + version = factorio_versions[release_channel.name][release_type.name] + 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 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) diff --git a/pkgs/games/factorio/versions.json b/pkgs/games/factorio/versions.json new file mode 100644 index 00000000000..82ab40ec3cb --- /dev/null +++ b/pkgs/games/factorio/versions.json @@ -0,0 +1,58 @@ +{ + "x86_64-linux": { + "alpha": { + "experimental": { + "name": "factorio_alpha_x64-1.1.2.tar.xz", + "needsAuth": true, + "sha256": "0cmia16d5dhy3f8mck926d7rrnavxmvb6a72ymjllxm37slsx60j", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/1.1.2/alpha/linux64", + "version": "1.1.2" + }, + "stable": { + "name": "factorio_alpha_x64-1.0.0.tar.xz", + "needsAuth": true, + "sha256": "0zixscff0svpb0yg8nzczp2z4filqqxi1k0z0nrpzn2hhzhf1464", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/1.0.0/alpha/linux64", + "version": "1.0.0" + } + }, + "demo": { + "experimental": { + "name": "factorio_demo_x64-1.0.0.tar.xz", + "needsAuth": false, + "sha256": "0h9cqbp143w47zcl4qg4skns4cngq0k40s5jwbk0wi5asjz8whqn", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/1.0.0/demo/linux64", + "version": "1.0.0" + }, + "stable": { + "name": "factorio_demo_x64-1.0.0.tar.xz", + "needsAuth": false, + "sha256": "0h9cqbp143w47zcl4qg4skns4cngq0k40s5jwbk0wi5asjz8whqn", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/1.0.0/demo/linux64", + "version": "1.0.0" + } + }, + "headless": { + "experimental": { + "name": "factorio_headless_x64-1.1.2.tar.xz", + "needsAuth": false, + "sha256": "0x3lwz11z8cczqr5i799m4yg8x3yk6h5qz48pfzw4l2ikrrwgahd", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/1.1.2/headless/linux64", + "version": "1.1.2" + }, + "stable": { + "name": "factorio_headless_x64-1.0.0.tar.xz", + "needsAuth": false, + "sha256": "0r0lplns8nxna2viv8qyx9mp4cckdvx6k20w2g2fwnj3jjmf3nc1", + "tarDirectory": "x64", + "url": "https://factorio.com/get-download/1.0.0/headless/linux64", + "version": "1.0.0" + } + } + } +}