chiark / gitweb /
3b673d83807ea4a71f356571d2664596abcf249a
[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 use Cwd;
46
47 my $self = $0;  $self =~ s{^.*/(?=.)}{};
48
49 my $worksphere = fastcwd // die "$self: Cwd::fastcwd failed: $!\n";
50 $worksphere =~ s{/[^/]+}{} or die "$self: cwd \`$worksphere' unsupported!\n";
51 our $lockfile = "../.nailing-cargo-sphere.lock";
52
53 our @configs;
54
55 sub read_or_enoent ($) {
56     my ($fn) = @_;
57     if (!open R, '<', $fn) {
58         return undef if $!==ENOENT;
59         die "$self: open $fn: $!\n";
60     }
61     local ($/) = undef;
62     my ($r) = <R> // die "$self: read $fn: $!\n";
63     $r;
64 }
65
66 sub toml_or_enoent ($$) {
67     my ($f,$what) = @_;
68     my $toml = read_or_enoent($f) // return;
69     my ($toml,$e) = from_toml($toml);
70     die "$self: parse TOML: $what: $f: $e\n" if defined $e;
71     $toml;
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("$HOME/$cfgleaf") if defined $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 $df;
94 }
95
96 sub lock () {
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]" == "@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 $e) {
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 unless defined $nail;
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{$manifesst}) {
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 lock();
194 readnail();
195 readorigs();
196
197 __DATA__
198
199 lock=${PWD%/*}/.nail.lock
200 if [ "x$NAILING_CARGO" != "x$lock" ]; then
201         NAILING_CARGO=$lock \
202         exec with-lock-ex -w "$lock" "$0" "$@"
203 fi
204
205 exec 203<../Cargo.nail
206 f=Cargo.toml
207
208 sed='
209 /^ *\[\(build-\)\?dependencies\]/,/^ \[/{
210 '
211
212 if test -e ../Cargo.nail-env; then
213         . ../Cargo.nail-env
214 fi
215
216 exec 204<../Cargo.nail
217 while read <&204 what where; do
218         if [ "x$what" = x- ]; then continue; fi
219         if [ "x$what" = 'x#' ]; then continue; fi
220         qwhere="${where//\//\\/}"
221         sed+='
222                 /{.*path *=/ b
223                 s/^'$what' *= *\(\".*\"\) *$/'$what' = { version = \1 }/;
224                 s#^'$what' *= *{#'$what' = { path = "'"${PWD%/*}"'/'"${qwhere}"'", #;
225                 /^'$what' *=/ s/version *= *\"[^"]*\"//;
226                 /^'$what' *=/ s/, *\([,}]\)/\1/;
227         '
228 done
229 sed+='}
230 '
231
232 exec 204<../Cargo.nail
233 while read <&204 what where; do
234         if [ "x$what" = 'x#' ]; then continue; fi
235         wf=../$where/$f
236         rm -f $wf.nailing~
237         sed <$wf >$wf.nailing~ "$sed"
238 done
239
240 exec 204<../Cargo.nail
241 while read <&204 what where; do
242         if [ "x$what" = 'x#' ]; then continue; fi
243         wf=../$where/$f
244         if ! test -e $wf.unnailed~; then
245                 ln $wf $wf.unnailed~
246         fi
247 done
248
249 trap '
250         set +e
251         while read <&203 what where; do
252                 if [ "x$what" = "x#" ]; then continue; fi
253                 wf=../$where/$f
254                 if test -e $wf.unnailed~; then
255                         rm -f $wf.nailed~
256                         ln $wf $wf.nailed~
257                         mv -f $wf.unnailed~ $wf
258                 fi
259         done
260         echo >&2 'Unnailed'
261 ' EXIT
262
263 exec 204<../Cargo.nail
264 printf >&2 'Nailing'
265 while read <&204 what where; do
266         if [ "x$what" = 'x#' ]; then continue; fi
267         wf=../$where/$f
268         printf >&2 ' %s' "$what"
269         if cmp -s $wf.nailed~ $wf.nailing~; then
270                 mv -f $wf.nailed~ $wf
271                 rm -f $wf.nailing
272         else
273                 mv -f $wf.nailing~ $wf
274                 rm -f $wf.nailed
275         fi
276 done
277 echo >&2
278
279 "$@"