3 # nailing-cargo: wrapper to use unpublished local crates
5 # Copyright (C) 2019-2020 Ian Jackson
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.
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.
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/>.
21 # ../nailing-cargo/nailing-caretwgo make
22 # ../nailing-cargo/nailing-cargo cargo build
23 # CARGO='../nailing-cargo/nailing-cargo cargo' make
25 # Why do we need this ?
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
36 # package = { subdir = ... }
44 use Fcntl qw(LOCK_EX);
47 my $self = $0; $self =~ s{^.*/(?=.)}{};
49 our $worksphere = getcwd() // die "$self: getcwd failed: $!\n";
50 $worksphere =~ s{/[^/]+}{} or die "$self: cwd \`$worksphere' unsupported!\n";
51 our $lockfile = "../.nailing-cargo-sphere.lock";
55 sub read_or_enoent ($) {
57 if (!open R, '<', $fn) {
58 return undef if $!==ENOENT;
59 die "$self: open $fn: $!\n";
62 my ($r) = <R> // die "$self: read $fn: $!\n";
66 sub toml_or_enoent ($$) {
68 my $toml = read_or_enoent($f) // return;
69 my ($v,$e) = from_toml($toml);
70 die "$self: parse TOML: $what: $f: $e\n" unless defined $v;
71 die "$e ?" if length $e;
77 my $toml = toml_or_enoent($f, "config file");
78 push @configs, $toml if defined $toml;
82 my $cfgleaf = ".nailing-cargo-cfg.toml";
83 load1config("/etc/nailing-cargo/cfg.toml");
84 load1config("$worksphere/$cfgleaf");
85 load1config("$ENV{HOME}/$cfgleaf") if defined $ENV{HOME};
90 foreach my $cfg (@configs) {
92 return $v if defined $v;
99 open LOCK, ">", $lockfile or die "$0: open/create $lockfile: $!\n";
100 flock LOCK, LOCK_EX or die "$0: lock $lockfile: $!\n";
101 my @fstat = stat LOCK or die "$0: fstat: $!\n";
102 my @stat = stat $lockfile;
104 next if $! == ENOENT;
105 die "$0: stat $lockfile: $!\n";
107 last if "@fstat[0..5]" eq "@stat[0..5]";
111 unlink $lockfile or die "$0: removing lockfile: $!\n";
117 my $nailfile = "../Cargo.nail";
118 open N, '<', $nailfile or die "$0: open $nailfile: $!\n";
120 my $toml = <N> // die "$0: read $nailfile: $!";
122 if ($toml !~ m{^\s*\[/}m &&
123 $toml !~ m{^[^\n\#]*\=}m &&
124 # old non-toml syntax
125 $toml =~ s{^[ \t]*([-_0-9a-z]+)[ \t]+(\S+)[ \t]*$}{$1 = \"$2\"}mig) {
126 $toml =~ s{^}{[packages\]\n};
130 ($nail,$e) = from_toml($toml);
131 if (!defined $nail) {
134 print STDERR "$self: $nailfile transformed into TOML:\n$toml\n";
136 die "$0: parse $nailfile: $e\n";
138 die "$e ?" if length $e;
144 sub read_manifest ($) {
146 my $manifest = "../$subdir/Cargo.toml";
147 if (defined $manifests{$manifest}) {
149 "$self: warning: $subdir: specified more than once!\n";
152 foreach my $try ("$manifest.unnailed", "$manifest") {
153 my $toml = toml_or_enoent($try, "package manifest") // next;
154 my $p = $toml->{package}{name};
157 "$self: warning: $subdir: missing package.name in $try, ignoring\n";
160 $manifests{$manifest} = $toml;
167 foreach my $p (keys %{ $nail->{packages} }) {
168 my $v = $nail->{packages}{$p};
169 my $subdir = ref($v) ? $v->{subdir} : $v;
170 my $gotpackage = read_manifest($subdir) // '<nothing!>';
171 if ($gotpackage ne $p) {
173 "$self: warning: honouring Cargo.nail [packages.$subdir]=$p even though $subdir contains package $gotpackage!\n";
175 die if defined $packagemap{$p};
176 $packagemap{$p} = $subdir;
178 foreach my $subdir (@{ $nail->{subdirs} }) {
179 my $gotpackage = read_manifest($subdir);
180 if (!defined $gotpackage) {
182 "$self: warning: ignoring subdir $subdir which has no Cargo.toml\n";
185 $packagemap{$gotpackage} //= $subdir;
190 foreach my $mf (keys %manifests) {
191 my $toml = $manifest{$mf};
192 foreach my $k (qw(dependencies build-dependencies dev-dependencies)) {
193 my $deps = $toml->{$k};
195 foreach my $p (keys %packagemap) {
196 my $info = $deps->{$p};
198 $info = { } unless ref $info;
199 delete $info->{version};
200 $info->{path} = $packagemap{$p};
203 my $nailing = "$mf.nailing~";
204 unlink $nailing or $!==ENOENT or die "$0: remove old $nailing: $!\n";
205 open N, '>', $nailing or die "$0: create new $nailing: $!\n";
206 print N, to_toml($toml) or die "$0: write new $nailing: $!\n";
207 close N or die "$0: close new $nailing: $!\n";
212 if ($want_uninstall) {
214 foreach my $mf (keys %manifests) {
215 eval { uninstall1($mf, 0); 1; }
216 or warn "$0: failed to revert $mf: $@\n";
222 foreach my $mf (keys %manifests) {
223 link "$mf", "$mf.unnailed" or $!==EEXIST
224 or die "$self: make backup link $mf.unnailed: $!\n";
229 foreach my $mf (keys %manifests) {
230 my $nailing = "$mf.nailing~";
231 my $nailed = "$mf.nailed~";
233 my $diff = compare($nailing, $nailed);
234 die "$0: compare $nailing and $nailed: $!" if $diff<0;
242 rename $use, $mf or die "$0: install nailed $use: $!\n";
243 unlink $rm or $!==ENOENT or die "$0: remove old $rm: $!\n";
248 my $r = system @ARGV;
252 print STDERR "$0: could not execute $ARGV[0]: $!\n";
254 } elsif ($r & 0xff00) {
255 print STDERR "$0: $ARGV[0] failed (exit status $r)\n";
258 print STDERR "$0: $ARGV[0] died due to signal! (wait status $r)\n";
263 sub uninstall1 ($$) {
264 my ($mf, $enoentok) = @_;
265 my $unnailed = "$mf.unnailed";
266 rename $unnailed, $mf or ($enoentok && $!==ENOENT)
267 die "$0: rename $unnailed back to $mf: $!\n";
271 foreach my $mf (keys %manifests) {
276 while (@ARGV && $ARGV[0] =~ m/^-/) {
289 my $estatus = invoke();
298 print STDERR Dumper(\%packagemap, \%manifests);
302 lock=${PWD%/*}/.nail.lock
303 if [ "x$NAILING_CARGO" != "x$lock" ]; then
304 NAILING_CARGO=$lock \
305 exec with-lock-ex -w "$lock" "$0" "$@"
308 exec 203<../Cargo.nail
312 /^ *\[\(build-\)\?dependencies\]/,/^ \[/{
315 if test -e ../Cargo.nail-env; then
319 exec 204<../Cargo.nail
320 while read <&204 what where; do
321 if [ "x$what" = x- ]; then continue; fi
322 if [ "x$what" = 'x#' ]; then continue; fi
323 qwhere="${where//\//\\/}"
326 s/^'$what' *= *\(\".*\"\) *$/'$what' = { version = \1 }/;
327 s#^'$what' *= *{#'$what' = { path = "'"${PWD%/*}"'/'"${qwhere}"'", #;
328 /^'$what' *=/ s/version *= *\"[^"]*\"//;
329 /^'$what' *=/ s/, *\([,}]\)/\1/;
335 exec 204<../Cargo.nail
336 while read <&204 what where; do
337 if [ "x$what" = 'x#' ]; then continue; fi
340 sed <$wf >$wf.nailing~ "$sed"
343 exec 204<../Cargo.nail
344 while read <&204 what where; do
345 if [ "x$what" = 'x#' ]; then continue; fi
347 if ! test -e $wf.unnailed~; then
354 while read <&203 what where; do
355 if [ "x$what" = "x#" ]; then continue; fi
357 if test -e $wf.unnailed~; then
360 mv -f $wf.unnailed~ $wf
366 exec 204<../Cargo.nail
368 while read <&204 what where; do
369 if [ "x$what" = 'x#' ]; then continue; fi
371 printf >&2 ' %s' "$what"
372 if cmp -s $wf.nailed~ $wf.nailing~; then
373 mv -f $wf.nailed~ $wf
376 mv -f $wf.nailing~ $wf