1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
{ depot, pkgs, lib, ... }:
# alloy-viz — headless Alloy analyzer with a browser frontend.
#
# Two binaries from one project:
#
# alloy-viz (Go) the thing you run: web UI and supervisor of the
# solver process.
# alloy-viz-solver (Java) a dumb worker speaking NUL-framed JSON on stdin/
# stdout, holding a warm JVM and a parsed model.
#
# The Java half is built with plain javac against the release jar from nixpkgs.
# Alloy upstream builds with Gradle+bnd, but we compile a single file against a
# handful of public classes (CompUtil, TranslateAlloyToKodkod, A4Solution,
# A4SolutionWriter, A4Reporter), so none of that is needed.
#
# We do not build Alloy itself: pkgs.alloy6 ships the jar, and the jar also
# bundles an unshaded gson, which the worker uses for its JSON framing rather
# than pulling in a dependency of its own.
let
goDeps = import ./go-deps.nix { inherit depot pkgs; };
jdk = pkgs.openjdk21;
alloyJar = "${pkgs.alloy6}/share/alloy/alloy6.jar";
solver = pkgs.stdenv.mkDerivation {
pname = "alloy-viz-solver";
version = "0.1";
src = ./java;
nativeBuildInputs = [ jdk pkgs.makeWrapper ];
buildPhase = ''
runHook preBuild
mkdir -p classes
javac -cp ${alloyJar} -d classes $(find src -name '*.java')
jar cf alloy-viz-solver.jar -C classes .
runHook postBuild
'';
installPhase = ''
runHook preInstall
mkdir -p $out/share/java $out/bin
cp alloy-viz-solver.jar $out/share/java/
makeWrapper ${jdk}/bin/java $out/bin/alloy-viz-solver \
--add-flags "-Djava.awt.headless=true" \
--add-flags "-Dorg.slf4j.simpleLogger.logFile=/dev/null" \
--add-flags "-cp ${alloyJar}:$out/share/java/alloy-viz-solver.jar" \
--add-flags "de.profpatsch.alloy.Worker"
runHook postInstall
'';
meta = {
description = "Headless Alloy solver worker for alloy-viz";
mainProgram = "alloy-viz-solver";
};
};
# -Djava.awt.headless=true keeps Swing out of the picture. The parser,
# translator and solver packages have no AWT imports at all, so this only
# guards against incidental initialisation.
#
# -Dorg.slf4j.simpleLogger.logFile=/dev/null is the *only* way to silence
# kodkod's progress chatter: it logs through slf4j-simple, and neither
# defaultLogLevel=off nor per-logger levels suppress it. Left unsilenced it
# would interleave with the frame stream on stderr.
ui = depot.nix.buildGo.program {
name = "alloy-viz";
srcs = [
./main.go
./worker.go
./jobs.go
./instance.go
./theme.go
./dot.go
./serve.go
./templates.go
./watch.go
];
deps = [
goDeps.fsnotify-fsnotify
];
};
in
pkgs.symlinkJoin {
name = "alloy-viz";
outputs = [ "out" "man" ];
paths = [ ui solver ];
buildInputs = [ pkgs.makeWrapper ];
postBuild = ''
# graphviz lays out the instance graphs; ALLOY_VIZ_SOLVER lets the Go half
# find its worker, so `nix run` works with no further setup.
wrapProgram $out/bin/alloy-viz \
--prefix PATH : ${lib.makeBinPath [ pkgs.graphviz ]} \
--set-default ALLOY_VIZ_SOLVER ${solver}/bin/alloy-viz-solver
mkdir -p $man/share/man/man1
cp ${./alloy-viz.1} $man/share/man/man1/alloy-viz.1
'';
meta = {
description = "View Alloy instances in a browser, without the Alloy GUI";
longDescription = ''
Replaces driving the Alloy Analyzer's Swing GUI by hand. Point alloy-viz
at a .als file and it solves the selected command, renders the instance
as a graph via graphviz, and re-solves whenever the file is saved.
The JVM is kept warm between runs, so repeated runs skip the JVM start
and parse that `alloy exec` pays every time. Solving happens in a
separate process because a running SAT4J solve cannot be interrupted
in-process; cancelling kills it.
'';
mainProgram = "alloy-viz";
};
}
|