aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/build_depends/dub2nix/src/dub2nix.d
diff options
context:
space:
mode:
Diffstat (limited to 'src/build_depends/dub2nix/src/dub2nix.d')
-rw-r--r--src/build_depends/dub2nix/src/dub2nix.d263
1 files changed, 263 insertions, 0 deletions
diff --git a/src/build_depends/dub2nix/src/dub2nix.d b/src/build_depends/dub2nix/src/dub2nix.d
new file mode 100644
index 0000000..e92ecf8
--- /dev/null
+++ b/src/build_depends/dub2nix/src/dub2nix.d
@@ -0,0 +1,263 @@
+#!/usr/bin/env dub
+/+ dub.sdl:
+ name "dub2nix"
+ stringImportPaths "."
+ dependency "vibe-d:data" version="*"
++/
+import vibe.data.json, std.string;
+
+enum mkDubNix = import("./mkDub.nix");
+enum VERSION = "0.2.4";
+enum HEADER = "# This file was generated by https://github.com/lionello/dub2nix v"~VERSION~"\n";
+
+unittest {
+ static assert(import("./dub2nix.nix").indexOf(VERSION) > 0, "VERSION does not match version in dub2nix.nix");
+}
+
+struct DubSelections {
+ int fileVersion;
+ string[string] versions;
+}
+
+struct DubRepo {
+ string owner;
+ string kind;
+ string project;
+}
+
+private string packageRegistry = "http://code.dlang.org/packages/";
+
+private auto download(string url) @trusted {
+version(none) {
+ // This works, but causes "leaking eventcore driver" warnings at shutdown
+ import vibe.http.client : requestHTTP;
+ scope res = requestHTTP();
+ return res.readJson();
+} else {
+ import std.net.curl: get, HTTP;
+ auto http = HTTP();
+ // Using deflate saves A LOT of traffic, ~40x
+ http.addRequestHeader("accept-encoding", "deflate");
+ http.addRequestHeader("accept", "application/json");
+ const data = get(url, http);
+ // Only accepting application/json, so anything else must be compressed
+ if (data[0] != '{') {
+ import std.zlib : uncompress;
+ return parseJsonString(cast(string)uncompress(data));
+ } else {
+ // parseJsonString takes immutable string, so need the .idup here
+ return parseJsonString(data.idup);
+ }
+}
+}
+
+/// Query Dub registry for the repository information of a package
+auto findRepo(string pname) @safe {
+ const url = packageRegistry ~ pname ~ ".json";
+ const json = download(url);
+ return deserializeJson!DubRepo(json["repository"]);
+}
+
+struct NixPrefetchGit {
+ @optional string type; /// set to "git", like Go deps.nix
+ string url; /// URL of GIT repository
+ string rev; /// sha1 or tag
+ string sha256; /// calculated by from nix-prefetch-git
+ @optional bool fetchSubmodules; /// optional; defaults to true
+ @optional string date; /// ignored; fetchgit doesn't actually want this
+ @optional bool deepClone; /// ignored
+ @optional bool leaveDotGit; /// ignored; if the .git directory should be preserved
+ @optional string path; /// ignored; a path in the Nix store?
+}
+
+/// Invoke nix-prefetch-git and return the parsed JSON
+auto nixPrefetchGit(string url, string rev) @safe {
+ import std.process : executeShell, Config;
+ const cmd = "nix-prefetch-git --quiet " ~ url ~ " " ~ rev;
+ return deserializeJson!NixPrefetchGit(
+ executeShell(cmd, null, Config.stderrPassThrough).output
+ );
+}
+
+struct DubDep {
+ NixPrefetchGit fetch; /// like Go deps.nix
+}
+
+/// Fetch the repo information for package `pname` and version `ver`
+auto prefetch(string pname, string ver) @safe {
+ const repo = findRepo(pname);
+ assert(repo.kind == "github");
+ const url = "https://" ~ repo.kind ~ ".com/" ~ repo.owner ~ '/' ~ repo.project ~ ".git";
+ const tag = "v" ~ ver;
+ auto set = nixPrefetchGit(url, tag);
+ // Overwrite the sha1 ref with the tag instead, so we have the version info as well
+ set.rev = tag;
+ set.type = "git";
+ return DubDep(set);
+}
+
+/// Convert D string to Nix string literal
+auto toNixString(in string s, int indent = 0) pure @safe {
+ if (s is null) {
+ return "null";
+ } else if (s.indexOfAny("\"\n") >= 0)
+ return "''\n" ~ s ~ "''";
+ else
+ return '"' ~ s ~ '"';
+}
+
+unittest {
+ static assert(toNixString(null) == "null");
+ static assert(toNixString("hello") == `"hello"`);
+ static assert(toNixString("with\nnewline") == "''\nwith\nnewline''");
+ static assert(toNixString(`with "quotes"`) == "''\nwith \"quotes\"''");
+}
+
+/// Convert D bool to Nix boolean literal
+auto toNixString(bool b, int indent = 0) pure @safe {
+ return b ? "true" : "false";
+}
+
+unittest {
+ static assert(toNixString(true) == "true");
+ static assert(toNixString(false) == "false");
+}
+
+private enum INDENT = " ";
+
+/// Convert D struct to Nix set
+auto toNixString(T)(in T pod, int indent = 0) pure @safe if (is(T == struct)) {
+ string prefix = INDENT[0..indent * 2 + 2];
+ string set = "{\n";
+ foreach(i, ref key; pod.tupleof) {
+ const id = __traits(identifier, pod.tupleof[i]);
+ set ~= prefix ~ id ~ " = " ~ toNixString(key, indent + 1) ~ ";\n";
+ }
+ return set ~ INDENT[0..indent * 2] ~ "}";
+}
+
+unittest {
+ struct TestStruct { bool b; }
+ static assert(toNixString(TestStruct.init) == "{\n b = false;\n}");
+ static assert(toNixString(TestStruct.init, 1) == "{\n b = false;\n }");
+}
+
+/// Convert D AArray to Nix set
+auto toNixString(T)(in T[string] aa, int indent = 0) pure @safe {
+ string prefix = INDENT[0..indent * 2 + 2];
+ string set = "{\n";
+ foreach(id, ref key; aa) {
+ set ~= prefix ~ id ~ " = " ~ toNixString(key, indent + 1) ~ ";\n";
+ }
+ return set ~ INDENT[0..indent * 2] ~ "}";
+}
+
+unittest {
+ static assert(toNixString(["s": "x"]) == "{\n s = \"x\";\n}");
+ static assert(toNixString(["s": ["x": true]]) == "{\n s = {\n x = true;\n };\n}");
+}
+
+/// Convert D array/range to Nix list
+import std.range : isForwardRange;
+auto toNixString(R)(in R range, int indent = 0) pure @safe if (isForwardRange!R && !is(R : string)) {
+ string list = "[ ";
+ foreach(const ref item; range) {
+ list ~= toNixString(item, indent) ~ " ";
+ }
+ return list ~ "]";
+}
+
+unittest {
+ static assert(toNixString(["a"]) == `[ "a" ]`);
+}
+
+/// Create Nix expression for all dependencies in the selections JSON
+auto createNixDeps(string selectionsJson) {
+ import std.parallelism : taskPool;
+ import std.array : byPair, array;
+ import std.stdio : writeln;
+
+ const selections = deserializeJson!DubSelections(selectionsJson);
+ assert(selections.fileVersion == 1);
+
+ static auto progress(Tuple)(in Tuple pair) {
+ writeln("# Prefetching ", pair.key, "-", pair.value);
+ return prefetch(pair.key, pair.value);
+ }
+
+ // Fetch all dependency information in parallel
+ debug scope(success) writeln("# Done.");
+ return HEADER ~ toNixString(taskPool.amap!progress(selections.versions.byPair.array));
+}
+
+unittest {
+ enum json = import("./dub.selections.json");
+ enum fixture = import("./dub.selections.nix");
+ assert(createNixDeps(json) == fixture);
+}
+
+// No "main" when we're running with unittests
+version(unittest) {} else {
+
+int main(string[] args) {
+ import std.stdio : writeln;
+ import std.file : readText, write;
+ import std.getopt: getopt, defaultGetoptPrinter;
+
+ bool showVersion;
+ string input = "./dub.selections.json", deps = "./dub.selections.nix", output;
+ auto result = getopt(args,
+ "input|i|in", "Path of selections JSON; defaults to " ~ input, &input,
+ "output|o|out", "Output Nix file for Dub project.", &output,
+ "registry|r", "URL to Dub package registry; default " ~ packageRegistry, &packageRegistry,
+ "deps-file|d", "Output Nix file with dependencies; defaults to " ~ deps, &deps,
+ "version", "Show version information.", &showVersion);
+
+ if (showVersion) {
+ writeln(VERSION);
+ return 0;
+ } else if (result.helpWanted || args.length != 2 || args[1] != "save") {
+ defaultGetoptPrinter(`Usage: dub2nix [OPTIONS] COMMAND
+
+Create Nix derivations for Dub package dependencies.
+
+Commands:
+ save Write Nix files for Dub project
+
+Options:`, result.options);
+ return 0;
+ }
+
+ try {
+ const nix = createNixDeps(readText(input));
+ if (deps == "-") {
+ writeln(nix);
+ } else {
+ write(deps, nix.representation);
+ }
+ if (output) {
+ write("mkDub.nix", mkDubNix);
+ write(output, HEADER ~ `
+{ pkgs ? import <nixpkgs> {} }:
+with import ./mkDub.nix { inherit pkgs; };
+
+mkDubDerivation {
+ src = ./.;
+ # version = "0.0.1";
+ # buildInputs = [ add any runtime deps here ];
+}
+`);
+ }
+ return 0;
+ } catch (Exception e) {
+ debug {
+ // Only dump callstack in debug builds
+ writeln(e.toString());
+ } else {
+ writeln("Error: ", e.message);
+ }
+ return 1;
+ }
+}
+
+}//!unittest