chiark / gitweb /
nailing-cargo: Partially converted to Perl..., now runs partway
[nailing-cargo.git] / nailing-cargo
1 #!/usr/bin/perl -w
2
3 #    nailing-cargo: wrapper to use unpublished local crates
4 #
5 #    Copyright (C) 2019-2020 Ian Jackson
6 #
7 #    This program is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU Affero General Public License as
9 #    published by the Free Software Foundation, either version 3 of the
10 #    License, or (at your option) any later version.
11 #
12 #    This program is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU Affero General Public License for more details.
16 #
17 #    You should have received a copy of the GNU Affero General Public License
18 #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 # example usages:
21 #   ../nailing-cargo/nailing-caretwgo make
22 #   ../nailing-cargo/nailing-cargo cargo build
23 #   CARGO='../nailing-cargo/nailing-cargo cargo' make
24
25 # Why do we need this ?
26 #
27 #  https://github.com/rust-lang/cargo/issues/6713
28 #  https://stackoverflow.com/questions/33025887/how-to-use-a-local-unpublished-crate
29 #  https://github.com/rust-lang/cargo/issues/1481
30
31
32 #: Cargo.nail:
33 #
34 #    [packages]
35 #    package = subdir
36 #    package = { subdir = ... }
37 #
38 #    [subdirs]
39 #    subdir
40
41 use strict;
42 use TOML;
43 use POSIX;
44 use Fcntl qw(LOCK_EX);
45
46 my $self = $0;  $self =~ s{^.*/(?=.)}{};
47
48 our $worksphere = getcwd() // die "$self: getcwd failed: $!\n";
49 $worksphere =~ s{/[^/]+}{} or die "$self: cwd \`$worksphere' unsupported!\n";
50 our $lockfile = "../.nailing-cargo-sphere.lock";
51
52 our @configs;
53
54 sub read_or_enoent ($) {
55     my ($fn) = @_;
56     if (!open R, '<', $fn) {
57         return undef if $!==ENOENT;
58         die "$self: open $fn: $!\n";
59     }
60     local ($/) = undef;
61     my ($r) = <R> // die "$self: read $fn: $!\n";
62     $r;
63 }
64
65 sub toml_or_enoent ($$) {
66     my ($f,$what) = @_;
67     my $toml = read_or_enoent($f) // return;
68     my ($v,$e) = from_toml($toml);
69     die "$self: parse TOML: $what: $f: $e\n" unless defined $v;
70     die "$e ?" if length $e;
71     $v;
72 }
73
74 sub load1config ($) {
75     my ($f) = @_;
76     my $toml = toml_or_enoent($f, "config file");
77     push @configs, $toml if defined $toml;
78 }
79
80 sub loadconfigs () {
81     my $cfgleaf = ".nailing-cargo-cfg.toml";
82     load1config("/etc/nailing-cargo/cfg.toml");
83     load1config("$worksphere/$cfgleaf");
84     load1config("$ENV{HOME}/$cfgleaf") if defined $ENV{HOME};
85 }
86
87 sub getcfg ($$) {
88     my ($k, $def) = @_;
89     foreach my $cfg (@configs) {
90         my $v = $cfg->{$k};
91         return $v if defined $v;
92     }
93     return $def;
94 }
95
96 sub takelock () {
97     for (;;) {
98         open LOCK, ">", $lockfile or die "$0: open/create $lockfile: $!\n";
99         flock LOCK, LOCK_EX or die "$0: lock $lockfile: $!\n";
100         my @fstat = stat LOCK or die "$0: fstat: $!\n";
101         my @stat  = stat $lockfile;
102         if (!@stat) {
103             next if $! == ENOENT;
104             die "$0: stat $lockfile: $!\n";
105         }
106         last if "@fstat[0..5]" eq "@stat[0..5]";
107     }
108 }
109 sub unlock () {
110     unlink $lockfile or die "$0: removing lockfile: $!\n";
111 }
112
113 our $nail;
114
115 sub readnail () {
116     my $nailfile = "../Cargo.nail";
117     open N, '<', $nailfile or die "$0: open $nailfile: $!\n";
118     local ($/) = undef;
119     my $toml = <N> // die "$0: read $nailfile: $!";
120     my $transformed;
121     if ($toml !~ m{^\s*\[/}m &&
122         $toml !~ m{^[^\n\#]*\=}m &&
123         # old non-toml syntax
124         $toml =~ s{^[ \t]*([-_0-9a-z]+)[ \t]+(\S+)[ \t]*$}{$1 = \"$2\"}mig) {
125         $toml =~ s{^}{[packages\]\n};
126         $transformed = 1;
127     }
128     my $e;
129     ($nail,$e) = from_toml($toml);
130     if (!defined $nail) {
131         if ($transformed) {
132             $toml =~ s/^/    /mg;
133             print STDERR "$self: $nailfile transformed into TOML:\n$toml\n";
134         }
135         die "$0: parse $nailfile: $e\n";
136     }
137     die "$e ?" if length $e;
138 }
139
140 our %manifests;
141 our %packagemap;
142
143 sub read_manifest ($) {
144     my ($subdir) = @_;
145     my $manifest = "../$subdir/Cargo.toml";
146     if (defined $manifests{$manifest}) {
147         print STDERR
148 "$self: warning: $subdir: specified more than once!\n";
149         return undef;
150     }
151     foreach my $try ("$manifest.unnailed", "$manifest") {
152         my $toml = toml_or_enoent($try, "package manifest") // next;
153         my $p = $toml->{package}{name};
154         if (!defined $p) {
155             print STDERR
156 "$self: warning: $subdir: missing package.name in $try, ignoring\n";
157             next;
158         }
159         $manifests{$manifest} = $toml;
160         return $p;
161     }
162     return undef;
163 }
164
165 sub readorigs () {
166     foreach my $p (keys %{ $nail->{packages} }) {
167         my $v = $nail->{packages}{$p};
168         my $subdir = ref($v) ? $v->{subdir} : $v;
169         my $gotpackage = read_manifest($subdir) // '<nothing!>';
170         if ($gotpackage ne $p) {
171             print STDERR
172  "$self: warning: honouring Cargo.nail [packages.$subdir]=$p even though $subdir contains package $gotpackage!\n";
173         }
174         die if defined $packagemap{$p};
175         $packagemap{$p} = $subdir;
176     }
177     foreach my $subdir (@{ $nail->{subdirs} }) {
178         my $gotpackage = read_manifest($subdir);
179         if (!defined $gotpackage) {
180             print STDERR
181  "$self: warning: ignoring subdir $subdir which has no Cargo.toml\n";
182             next;
183         }
184         $packagemap{$gotpackage} //= $subdir;
185     }
186 }
187
188 while (@ARGV && $ARGV[0] =~ m/^-/) {
189     $_ = shift @ARGV;
190     last if m/^--$/;
191 }
192
193 takelock();
194 readnail();
195 readorigs();
196
197 use Data::Dumper;
198 print STDERR Dumper(\%packagemap, \%manifests);
199
200 __DATA__
201
202 lock=${PWD%/*}/.nail.lock
203 if [ "x$NAILING_CARGO" != "x$lock" ]; then
204         NAILING_CARGO=$lock \
205         exec with-lock-ex -w "$lock" "$0" "$@"
206 fi
207
208 exec 203<../Cargo.nail
209 f=Cargo.toml
210
211 sed='
212 /^ *\[\(build-\)\?dependencies\]/,/^ \[/{
213 '
214
215 if test -e ../Cargo.nail-env; then
216         . ../Cargo.nail-env
217 fi
218
219 exec 204<../Cargo.nail
220 while read <&204 what where; do
221         if [ "x$what" = x- ]; then continue; fi
222         if [ "x$what" = 'x#' ]; then continue; fi
223         qwhere="${where//\//\\/}"
224         sed+='
225                 /{.*path *=/ b
226                 s/^'$what' *= *\(\".*\"\) *$/'$what' = { version = \1 }/;
227                 s#^'$what' *= *{#'$what' = { path = "'"${PWD%/*}"'/'"${qwhere}"'", #;
228                 /^'$what' *=/ s/version *= *\"[^"]*\"//;
229                 /^'$what' *=/ s/, *\([,}]\)/\1/;
230         '
231 done
232 sed+='}
233 '
234
235 exec 204<../Cargo.nail
236 while read <&204 what where; do
237         if [ "x$what" = 'x#' ]; then continue; fi
238         wf=../$where/$f
239         rm -f $wf.nailing~
240         sed <$wf >$wf.nailing~ "$sed"
241 done
242
243 exec 204<../Cargo.nail
244 while read <&204 what where; do
245         if [ "x$what" = 'x#' ]; then continue; fi
246         wf=../$where/$f
247         if ! test -e $wf.unnailed~; then
248                 ln $wf $wf.unnailed~
249         fi
250 done
251
252 trap '
253         set +e
254         while read <&203 what where; do
255                 if [ "x$what" = "x#" ]; then continue; fi
256                 wf=../$where/$f
257                 if test -e $wf.unnailed~; then
258                         rm -f $wf.nailed~
259                         ln $wf $wf.nailed~
260                         mv -f $wf.unnailed~ $wf
261                 fi
262         done
263         echo >&2 'Unnailed'
264 ' EXIT
265
266 exec 204<../Cargo.nail
267 printf >&2 'Nailing'
268 while read <&204 what where; do
269         if [ "x$what" = 'x#' ]; then continue; fi
270         wf=../$where/$f
271         printf >&2 ' %s' "$what"
272         if cmp -s $wf.nailed~ $wf.nailing~; then
273                 mv -f $wf.nailed~ $wf
274                 rm -f $wf.nailing
275         else
276                 mv -f $wf.nailing~ $wf
277                 rm -f $wf.nailed
278         fi
279 done
280 echo >&2
281
282 "$@"