1{ pkgs, config, buildPackages, lib, stdenv, libiconv, mkNugetDeps, mkNugetSource, gixy }:
2
3let
4 aliases = if config.allowAliases then (import ./aliases.nix lib) else prev: {};
5
6 writers = with lib; rec {
7 # Base implementation for non-compiled executables.
8 # Takes an interpreter, for example `${pkgs.bash}/bin/bash`
9 #
10 # Examples:
11 # writeBash = makeScriptWriter { interpreter = "${pkgs.bash}/bin/bash"; }
12 # makeScriptWriter { interpreter = "${pkgs.dash}/bin/dash"; } "hello" "echo hello world"
13 makeScriptWriter = { interpreter, check ? "" }: nameOrPath: content:
14 assert lib.or (types.path.check nameOrPath) (builtins.match "([0-9A-Za-z._])[0-9A-Za-z._-]*" nameOrPath != null);
15 assert lib.or (types.path.check content) (types.str.check content);
16 let
17 name = last (builtins.split "/" nameOrPath);
18 in
19
20 pkgs.runCommandLocal name (if (types.str.check content) then {
21 inherit content interpreter;
22 passAsFile = [ "content" ];
23 } else {
24 inherit interpreter;
25 contentPath = content;
26 }) ''
27 # On darwin a script cannot be used as an interpreter in a shebang but
28 # there doesn't seem to be a limit to the size of shebang and multiple
29 # arguments to the interpreter are allowed.
30 if [[ -n "${toString pkgs.stdenvNoCC.isDarwin}" ]] && isScript $interpreter
31 then
32 wrapperInterpreterLine=$(head -1 "$interpreter" | tail -c+3)
33 # Get first word from the line (note: xargs echo remove leading spaces)
34 wrapperInterpreter=$(echo "$wrapperInterpreterLine" | xargs echo | cut -d " " -f1)
35
36 if isScript $wrapperInterpreter
37 then
38 echo "error: passed interpreter ($interpreter) is a script which has another script ($wrapperInterpreter) as an interpreter, which is not supported."
39 exit 1
40 fi
41
42 # This should work as long as wrapperInterpreter is a shell, which is
43 # the case for programs wrapped with makeWrapper, like
44 # python3.withPackages etc.
45 interpreterLine="$wrapperInterpreterLine $interpreter"
46 else
47 interpreterLine=$interpreter
48 fi
49
50 echo "#! $interpreterLine" > $out
51 cat "$contentPath" >> $out
52 ${optionalString (check != "") ''
53 ${check} $out
54 ''}
55 chmod +x $out
56 ${optionalString (types.path.check nameOrPath) ''
57 mv $out tmp
58 mkdir -p $out/$(dirname "${nameOrPath}")
59 mv tmp $out/${nameOrPath}
60 ''}
61 '';
62
63 # Base implementation for compiled executables.
64 # Takes a compile script, which in turn takes the name as an argument.
65 #
66 # Examples:
67 # writeSimpleC = makeBinWriter { compileScript = name: "gcc -o $out $contentPath"; }
68 makeBinWriter = { compileScript, strip ? true }: nameOrPath: content:
69 assert lib.or (types.path.check nameOrPath) (builtins.match "([0-9A-Za-z._])[0-9A-Za-z._-]*" nameOrPath != null);
70 assert lib.or (types.path.check content) (types.str.check content);
71 let
72 name = last (builtins.split "/" nameOrPath);
73 in
74 pkgs.runCommand name ((if (types.str.check content) then {
75 inherit content;
76 passAsFile = [ "content" ];
77 } else {
78 contentPath = content;
79 }) // lib.optionalAttrs (stdenv.hostPlatform.isDarwin && stdenv.hostPlatform.isAarch64) {
80 # post-link-hook expects codesign_allocate to be in PATH
81 # https://github.com/NixOS/nixpkgs/issues/154203
82 # https://github.com/NixOS/nixpkgs/issues/148189
83 nativeBuildInputs = [ stdenv.cc.bintools ];
84 }) ''
85 ${compileScript}
86 ${lib.optionalString strip
87 "${lib.getBin buildPackages.bintools-unwrapped}/bin/${buildPackages.bintools-unwrapped.targetPrefix}strip -S $out"}
88 # Sometimes binaries produced for darwin (e. g. by GHC) won't be valid
89 # mach-o executables from the get-go, but need to be corrected somehow
90 # which is done by fixupPhase.
91 ${lib.optionalString pkgs.stdenvNoCC.hostPlatform.isDarwin "fixupPhase"}
92 ${optionalString (types.path.check nameOrPath) ''
93 mv $out tmp
94 mkdir -p $out/$(dirname "${nameOrPath}")
95 mv tmp $out/${nameOrPath}
96 ''}
97 '';
98
99 # Like writeScript but the first line is a shebang to bash
100 #
101 # Example:
102 # writeBash "example" ''
103 # echo hello world
104 # ''
105 writeBash = makeScriptWriter {
106 interpreter = "${pkgs.bash}/bin/bash";
107 };
108
109 # Like writeScriptBin but the first line is a shebang to bash
110 writeBashBin = name:
111 writeBash "/bin/${name}";
112
113 # Like writeScript but the first line is a shebang to dash
114 #
115 # Example:
116 # writeDash "example" ''
117 # echo hello world
118 # ''
119 writeDash = makeScriptWriter {
120 interpreter = "${pkgs.dash}/bin/dash";
121 };
122
123 # Like writeScriptBin but the first line is a shebang to dash
124 writeDashBin = name:
125 writeDash "/bin/${name}";
126
127 # Like writeScript but the first line is a shebang to fish
128 #
129 # Example:
130 # writeFish "example" ''
131 # echo hello world
132 # ''
133 writeFish = makeScriptWriter {
134 interpreter = "${pkgs.fish}/bin/fish --no-config";
135 check = "${pkgs.fish}/bin/fish --no-config --no-execute"; # syntax check only
136 };
137
138 # Like writeScriptBin but the first line is a shebang to fish
139 writeFishBin = name:
140 writeFish "/bin/${name}";
141
142 # writeHaskell takes a name, an attrset with libraries and haskell version (both optional)
143 # and some haskell source code and returns an executable.
144 #
145 # Example:
146 # writeHaskell "missiles" { libraries = [ pkgs.haskellPackages.acme-missiles ]; } ''
147 # import Acme.Missiles
148 #
149 # main = launchMissiles
150 # '';
151 writeHaskell = name: {
152 libraries ? [],
153 ghc ? pkgs.ghc,
154 ghcArgs ? [],
155 threadedRuntime ? true,
156 strip ? true
157 }:
158 let
159 appendIfNotSet = el: list: if elem el list then list else list ++ [ el ];
160 ghcArgs' = if threadedRuntime then appendIfNotSet "-threaded" ghcArgs else ghcArgs;
161
162 in makeBinWriter {
163 compileScript = ''
164 cp $contentPath tmp.hs
165 ${ghc.withPackages (_: libraries )}/bin/ghc ${lib.escapeShellArgs ghcArgs'} tmp.hs
166 mv tmp $out
167 '';
168 inherit strip;
169 } name;
170
171 # writeHaskellBin takes the same arguments as writeHaskell but outputs a directory (like writeScriptBin)
172 writeHaskellBin = name:
173 writeHaskell "/bin/${name}";
174
175 writeRust = name: {
176 rustc ? pkgs.rustc,
177 rustcArgs ? [],
178 strip ? true
179 }:
180 let
181 darwinArgs = lib.optionals stdenv.isDarwin [ "-L${lib.getLib libiconv}/lib" ];
182 in
183 makeBinWriter {
184 compileScript = ''
185 cp "$contentPath" tmp.rs
186 PATH=${makeBinPath [pkgs.gcc]} ${lib.getBin rustc}/bin/rustc ${lib.escapeShellArgs rustcArgs} ${lib.escapeShellArgs darwinArgs} -o "$out" tmp.rs
187 '';
188 inherit strip;
189 } name;
190
191 writeRustBin = name:
192 writeRust "/bin/${name}";
193
194 # writeJS takes a name an attributeset with libraries and some JavaScript sourcecode and
195 # returns an executable
196 #
197 # Example:
198 # writeJS "example" { libraries = [ pkgs.nodePackages.uglify-js ]; } ''
199 # var UglifyJS = require("uglify-js");
200 # var code = "function add(first, second) { return first + second; }";
201 # var result = UglifyJS.minify(code);
202 # console.log(result.code);
203 # ''
204 writeJS = name: { libraries ? [] }: content:
205 let
206 node-env = pkgs.buildEnv {
207 name = "node";
208 paths = libraries;
209 pathsToLink = [
210 "/lib/node_modules"
211 ];
212 };
213 in writeDash name ''
214 export NODE_PATH=${node-env}/lib/node_modules
215 exec ${pkgs.nodejs}/bin/node ${pkgs.writeText "js" content} "$@"
216 '';
217
218 # writeJSBin takes the same arguments as writeJS but outputs a directory (like writeScriptBin)
219 writeJSBin = name:
220 writeJS "/bin/${name}";
221
222 awkFormatNginx = builtins.toFile "awkFormat-nginx.awk" ''
223 awk -f
224 {sub(/^[ \t]+/,"");idx=0}
225 /\{/{ctx++;idx=1}
226 /\}/{ctx--}
227 {id="";for(i=idx;i<ctx;i++)id=sprintf("%s%s", id, "\t");printf "%s%s\n", id, $0}
228 '';
229
230 writeNginxConfig = name: text: pkgs.runCommandLocal name {
231 inherit text;
232 passAsFile = [ "text" ];
233 nativeBuildInputs = [ gixy ];
234 } /* sh */ ''
235 # nginx-config-formatter has an error - https://github.com/1connect/nginx-config-formatter/issues/16
236 awk -f ${awkFormatNginx} "$textPath" | sed '/^\s*$/d' > $out
237 gixy $out
238 '';
239
240 # writePerl takes a name an attributeset with libraries and some perl sourcecode and
241 # returns an executable
242 #
243 # Example:
244 # writePerl "example" { libraries = [ pkgs.perlPackages.boolean ]; } ''
245 # use boolean;
246 # print "Howdy!\n" if true;
247 # ''
248 writePerl = name: { libraries ? [] }:
249 makeScriptWriter {
250 interpreter = "${pkgs.perl.withPackages (p: libraries)}/bin/perl";
251 } name;
252
253 # writePerlBin takes the same arguments as writePerl but outputs a directory (like writeScriptBin)
254 writePerlBin = name:
255 writePerl "/bin/${name}";
256
257 # makePythonWriter takes python and compatible pythonPackages and produces python script writer,
258 # which validates the script with flake8 at build time. If any libraries are specified,
259 # python.withPackages is used as interpreter, otherwise the "bare" python is used.
260 makePythonWriter = python: pythonPackages: buildPythonPackages: name: { libraries ? [], flakeIgnore ? [] }:
261 let
262 ignoreAttribute = optionalString (flakeIgnore != []) "--ignore ${concatMapStringsSep "," escapeShellArg flakeIgnore}";
263 in
264 makeScriptWriter {
265 interpreter =
266 if libraries == []
267 then "${python}/bin/python"
268 else "${python.withPackages (ps: libraries)}/bin/python"
269 ;
270 check = optionalString python.isPy3k (writeDash "pythoncheck.sh" ''
271 exec ${buildPythonPackages.flake8}/bin/flake8 --show-source ${ignoreAttribute} "$1"
272 '');
273 } name;
274
275 # writePyPy2 takes a name an attributeset with libraries and some pypy2 sourcecode and
276 # returns an executable
277 #
278 # Example:
279 # writePyPy2 "test_pypy2" { libraries = [ pkgs.pypy2Packages.enum ]; } ''
280 # from enum import Enum
281 #
282 # class Test(Enum):
283 # a = "success"
284 #
285 # print Test.a
286 # ''
287 writePyPy2 = makePythonWriter pkgs.pypy2 pkgs.pypy2Packages buildPackages.pypy2Packages;
288
289 # writePyPy2Bin takes the same arguments as writePyPy2 but outputs a directory (like writeScriptBin)
290 writePyPy2Bin = name:
291 writePyPy2 "/bin/${name}";
292
293 # writePython3 takes a name an attributeset with libraries and some python3 sourcecode and
294 # returns an executable
295 #
296 # Example:
297 # writePython3 "test_python3" { libraries = [ pkgs.python3Packages.pyyaml ]; } ''
298 # import yaml
299 #
300 # y = yaml.load("""
301 # - test: success
302 # """)
303 # print(y[0]['test'])
304 # ''
305 writePython3 = makePythonWriter pkgs.python3 pkgs.python3Packages buildPackages.python3Packages;
306
307 # writePython3Bin takes the same arguments as writePython3 but outputs a directory (like writeScriptBin)
308 writePython3Bin = name:
309 writePython3 "/bin/${name}";
310
311 # writePyPy3 takes a name an attributeset with libraries and some pypy3 sourcecode and
312 # returns an executable
313 #
314 # Example:
315 # writePyPy3 "test_pypy3" { libraries = [ pkgs.pypy3Packages.pyyaml ]; } ''
316 # import yaml
317 #
318 # y = yaml.load("""
319 # - test: success
320 # """)
321 # print(y[0]['test'])
322 # ''
323 writePyPy3 = makePythonWriter pkgs.pypy3 pkgs.pypy3Packages buildPackages.pypy3Packages;
324
325 # writePyPy3Bin takes the same arguments as writePyPy3 but outputs a directory (like writeScriptBin)
326 writePyPy3Bin = name:
327 writePyPy3 "/bin/${name}";
328
329
330 makeFSharpWriter = { dotnet-sdk ? pkgs.dotnet-sdk, fsi-flags ? "", libraries ? _: [] }: nameOrPath:
331 let
332 fname = last (builtins.split "/" nameOrPath);
333 path = if strings.hasSuffix ".fsx" nameOrPath then nameOrPath else "${nameOrPath}.fsx";
334 _nugetDeps = mkNugetDeps { name = "${fname}-nuget-deps"; nugetDeps = libraries; };
335
336 nuget-source = mkNugetSource {
337 name = "${fname}-nuget-source";
338 description = "A Nuget source with the dependencies for ${fname}";
339 deps = [ _nugetDeps ];
340 };
341
342 fsi = writeBash "fsi" ''
343 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
344 export DOTNET_CLI_TELEMETRY_OPTOUT=1
345 export DOTNET_NOLOGO=1
346 script="$1"; shift
347 ${dotnet-sdk}/bin/dotnet fsi --quiet --nologo --readline- ${fsi-flags} "$@" < "$script"
348 '';
349
350 in content: writers.makeScriptWriter {
351 interpreter = fsi;
352 } path
353 ''
354 #i "nuget: ${nuget-source}/lib"
355 ${ content }
356 exit 0
357 '';
358
359 writeFSharp =
360 makeFSharpWriter {};
361
362 writeFSharpBin = name:
363 writeFSharp "/bin/${name}";
364
365};
366in
367writers // (aliases writers)