Merge pull request #268619 from tweag/lib-descriptions
[NixPkgs.git] / pkgs / games / factorio / default.nix
blob7851cd6e8d1e8e422ba8f200e7f1639621f52ef5
1 { lib
2 , alsa-lib
3 , factorio-utils
4 , fetchurl
5 , libGL
6 , libICE
7 , libSM
8 , libX11
9 , libXcursor
10 , libXext
11 , libXi
12 , libXinerama
13 , libXrandr
14 , libpulseaudio
15 , libxkbcommon
16 , makeDesktopItem
17 , makeWrapper
18 , releaseType
19 , stdenv
20 , wayland
22 , mods-dat ? null
23 , versionsJson ? ./versions.json
24 , username ? ""
25 , token ? "" # get/reset token at https://factorio.com/profile
26 , experimental ? false # true means to always use the latest branch
27 , ...
28 } @ args:
30 assert releaseType == "alpha"
31   || releaseType == "headless"
32   || releaseType == "demo";
34 let
36   inherit (lib) importJSON;
38   mods = args.mods or [ ];
40   helpMsg = ''
42     ===FETCH FAILED===
43     Please ensure you have set the username and token with config.nix, or
44     /etc/nix/nixpkgs-config.nix if on NixOS.
46     Your token can be seen at https://factorio.com/profile (after logging in). It is
47     not as sensitive as your password, but should still be safeguarded. There is a
48     link on that page to revoke/invalidate the token, if you believe it has been
49     leaked or wish to take precautions.
51     Example:
52     {
53       packageOverrides = pkgs: {
54         factorio = pkgs.factorio.override {
55           username = "FactorioPlayer1654";
56           token = "d5ad5a8971267c895c0da598688761";
57         };
58       };
59     }
61     Alternatively, instead of providing the username+token, you may manually
62     download the release through https://factorio.com/download , then add it to
63     the store using e.g.:
65       releaseType=alpha
66       version=0.17.74
67       nix-prefetch-url file://\''$HOME/Downloads/factorio_\''${releaseType}_x64_\''${version}.tar.xz --name factorio_\''${releaseType}_x64-\''${version}.tar.xz
69     Note the ultimate "_" is replaced with "-" in the --name arg!
70   '';
72   desktopItem = makeDesktopItem {
73     name = "factorio";
74     desktopName = "Factorio";
75     comment = "A game in which you build and maintain factories.";
76     exec = "factorio";
77     icon = "factorio";
78     categories = [ "Game" ];
79   };
81   branch = if experimental then "experimental" else "stable";
83   # NB `experimental` directs us to take the latest build, regardless of its branch;
84   # hence the (stable, experimental) pairs may sometimes refer to the same distributable.
85   versions = importJSON versionsJson;
86   binDists = makeBinDists versions;
88   actual = binDists.${stdenv.hostPlatform.system}.${releaseType}.${branch} or (throw "Factorio ${releaseType}-${branch} binaries for ${stdenv.hostPlatform.system} are not available for download.");
90   makeBinDists = versions:
91     let
92       f = path: name: value:
93         if builtins.isAttrs value then
94           if value ? "name" then
95             makeBinDist value
96           else
97             builtins.mapAttrs (f (path ++ [ name ])) value
98         else
99           throw "expected attrset at ${toString path} - got ${toString value}";
100     in
101     builtins.mapAttrs (f [ ]) versions;
102   makeBinDist = { name, version, tarDirectory, url, sha256, needsAuth }: {
103     inherit version tarDirectory;
104     src =
105       if !needsAuth then
106         fetchurl { inherit name url sha256; }
107       else
108         (lib.overrideDerivation
109           (fetchurl {
110             inherit name url sha256;
111             curlOptsList = [
112               "--get"
113               "--data-urlencode"
114               "username@username"
115               "--data-urlencode"
116               "token@token"
117             ];
118           })
119           (_: {
120             # This preHook hides the credentials from /proc
121             preHook =
122               if username != "" && token != "" then ''
123                 echo -n "${username}" >username
124                 echo -n "${token}"    >token
125               '' else ''
126                 # Deliberately failing since username/token was not provided, so we can't fetch.
127                 # We can't use builtins.throw since we want the result to be used if the tar is in the store already.
128                 exit 1
129               '';
130             failureHook = ''
131               cat <<EOF
132               ${helpMsg}
133               EOF
134             '';
135           }));
136   };
138   configBaseCfg = ''
139     use-system-read-write-data-directories=false
140     [path]
141     read-data=$out/share/factorio/data/
142     [other]
143     check_updates=false
144   '';
146   updateConfigSh = ''
147     #! $SHELL
148     if [[ -e ~/.factorio/config.cfg ]]; then
149       # Config file exists, but may have wrong path.
150       # Try to edit it. I'm sure this is perfectly safe and will never go wrong.
151       sed -i 's|^read-data=.*|read-data=$out/share/factorio/data/|' ~/.factorio/config.cfg
152     else
153       # Config file does not exist. Phew.
154       install -D $out/share/factorio/config-base.cfg ~/.factorio/config.cfg
155     fi
156   '';
158   modDir = factorio-utils.mkModDirDrv mods mods-dat;
160   base = with actual; {
161     pname = "factorio-${releaseType}";
162     inherit version src;
164     preferLocalBuild = true;
165     dontBuild = true;
167     installPhase = ''
168       mkdir -p $out/{bin,share/factorio}
169       cp -a data $out/share/factorio
170       cp -a bin/${tarDirectory}/factorio $out/bin/factorio
171       patchelf \
172         --set-interpreter $(cat $NIX_CC/nix-support/dynamic-linker) \
173         $out/bin/factorio
174     '';
176     passthru.updateScript =
177       if (username != "" && token != "") then [
178         ./update.py
179         "--username=${username}"
180         "--token=${token}"
181       ] else null;
183     meta = {
184       description = "A game in which you build and maintain factories";
185       longDescription = ''
186         Factorio is a game in which you build and maintain factories.
188         You will be mining resources, researching technologies, building
189         infrastructure, automating production and fighting enemies. Use your
190         imagination to design your factory, combine simple elements into
191         ingenious structures, apply management skills to keep it working and
192         finally protect it from the creatures who don't really like you.
194         Factorio has been in development since spring of 2012, and reached
195         version 1.0 in mid 2020.
196       '';
197       homepage = "https://www.factorio.com/";
198       sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
199       license = lib.licenses.unfree;
200       maintainers = with lib.maintainers; [ Baughn elitak erictapen priegger lukegb ];
201       platforms = [ "x86_64-linux" ];
202     };
203   };
205   releases = rec {
206     headless = base;
207     demo = base // {
209       nativeBuildInputs = [ makeWrapper ];
210       buildInputs = [ libpulseaudio ];
212       libPath = lib.makeLibraryPath [
213         alsa-lib
214         libGL
215         libICE
216         libSM
217         libX11
218         libXcursor
219         libXext
220         libXi
221         libXinerama
222         libXrandr
223         libpulseaudio
224         libxkbcommon
225         wayland
226       ];
228       installPhase = base.installPhase + ''
229         wrapProgram $out/bin/factorio                                \
230           --prefix LD_LIBRARY_PATH : /run/opengl-driver/lib:$libPath \
231           --run "$out/share/factorio/update-config.sh"               \
232           --argv0 ""                                                 \
233           --add-flags "-c \$HOME/.factorio/config.cfg"               \
234           ${lib.optionalString (mods!=[]) "--add-flags --mod-directory=${modDir}"}
236           # TODO Currently, every time a mod is changed/added/removed using the
237           # modlist, a new derivation will take up the entire footprint of the
238           # client. The only way to avoid this is to remove the mods arg from the
239           # package function. The modsDir derivation will have to be built
240           # separately and have the user specify it in the .factorio config or
241           # right along side it using a symlink into the store I think i will
242           # just remove mods for the client derivation entirely. this is much
243           # cleaner and more useful for headless mode.
245           # TODO: trying to toggle off a mod will result in read-only-fs-error.
246           # not much we can do about that except warn the user somewhere. In
247           # fact, no exit will be clean, since this error will happen on close
248           # regardless. just prints an ugly stacktrace but seems to be otherwise
249           # harmless, unless maybe the user forgets and tries to use the mod
250           # manager.
252         install -m0644 <(cat << EOF
253         ${configBaseCfg}
254         EOF
255         ) $out/share/factorio/config-base.cfg
257         install -m0755 <(cat << EOF
258         ${updateConfigSh}
259         EOF
260         ) $out/share/factorio/update-config.sh
262         mkdir -p $out/share/icons/hicolor/{64x64,128x128}/apps
263         cp -a data/core/graphics/factorio-icon.png $out/share/icons/hicolor/64x64/apps/factorio.png
264         cp -a data/core/graphics/factorio-icon@2x.png $out/share/icons/hicolor/128x128/apps/factorio.png
265         ln -s ${desktopItem}/share/applications $out/share/
266       '';
267     };
268     alpha = demo // {
270       installPhase = demo.installPhase + ''
271         cp -a doc-html $out/share/factorio
272       '';
273     };
274   };
277 stdenv.mkDerivation (releases.${releaseType})