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
37 # package = { subdir = ... }
46 use Fcntl qw(LOCK_EX);
49 my $self = $0; $self =~ s{^.*/(?=.)}{};
51 our $worksphere = getcwd() // die "$self: getcwd failed: $!\n";
52 $worksphere =~ s{/[^/]+}{} or die "$self: cwd \`$worksphere' unsupported!\n";
53 our $lockfile = "../.nailing-cargo-sphere.lock";
56 our ($verbose,$noact,$dump);
58 sub read_or_enoent ($) {
60 if (!open R, '<', $fn) {
61 return undef if $!==ENOENT;
62 die "$self: open $fn: $!\n";
65 my ($r) = <R> // die "$self: read $fn: $!\n";
69 sub toml_or_enoent ($$) {
71 my $parser = TOML::Parser->new();
72 my $toml = read_or_enoent($f) // return;
73 my $v = $parser->parse($toml);
74 # die "$self: parse TOML: $what: $f: $e\n" unless defined $v;
75 # die "$e ?" if length $e;
81 my $toml = toml_or_enoent($f, "config file");
82 push @configs, $toml if defined $toml;
86 my $cfgleaf = ".nailing-cargo-cfg.toml";
87 load1config("/etc/nailing-cargo/cfg.toml");
88 load1config("$worksphere/$cfgleaf");
89 load1config("$ENV{HOME}/$cfgleaf") if defined $ENV{HOME};
94 foreach my $cfg (@configs) {
96 return $v if defined $v;
103 open LOCK, ">", $lockfile or die "$self: open/create $lockfile: $!\n";
104 flock LOCK, LOCK_EX or die "$self: lock $lockfile: $!\n";
105 my @fstat = stat LOCK or die "$self: fstat: $!\n";
106 my @stat = stat $lockfile;
108 next if $! == ENOENT;
109 die "$self: stat $lockfile: $!\n";
111 last if "@fstat[0..5]" eq "@stat[0..5]";
115 unlink $lockfile or die "$self: removing lockfile: $!\n";
121 my $nailfile = "../Cargo.nail";
122 open N, '<', $nailfile or die "$self: open $nailfile: $!\n";
124 my $toml = <N> // die "$self: read $nailfile: $!";
126 if ($toml !~ m{^\s*\[/}m &&
127 $toml !~ m{^[^\n\#]*\=}m &&
128 # old non-toml syntax
129 $toml =~ s{^[ \t]*([-_0-9a-z]+)[ \t]+(\S+)[ \t]*$}{$1 = \"$2\"}mig) {
130 $toml =~ s{^}{[packages\]\n};
132 $toml =~ s{^[ \t]*\-[ \t]*\=[ \t]*(\"[-_0-9a-z]+\"\n?)$}{
135 $toml = "subdirs = [\n".(join '', map { "$_\n" } @sd)."]\n".$toml;
139 ($nail,$e) = from_toml($toml);
140 if (!defined $nail) {
143 print STDERR "$self: $nailfile transformed into TOML:\n$toml\n";
145 die "$self: parse $nailfile: $e\n";
147 die "$e ?" if length $e;
153 sub read_manifest ($) {
155 my $manifest = "../$subdir/Cargo.toml";
156 print STDERR "$self: reading $manifest...\n" if $verbose>=3;
157 if (defined $manifests{$manifest}) {
159 "$self: warning: $subdir: specified more than once!\n";
162 foreach my $try ("$manifest.unnailed", "$manifest") {
163 my $toml = toml_or_enoent($try, "package manifest") // next;
164 my $p = $toml->{package}{name};
167 "$self: warning: $subdir: missing package.name in $try, ignoring\n";
170 $manifests{$manifest} = $toml;
177 foreach my $p (keys %{ $nail->{packages} }) {
178 my $v = $nail->{packages}{$p};
179 my $subdir = ref($v) ? $v->{subdir} : $v;
180 my $gotpackage = read_manifest($subdir) // '<nothing!>';
181 if ($gotpackage ne $p) {
183 "$self: warning: honouring Cargo.nail packages.$subdir=$p even though $subdir contains package $gotpackage!\n";
185 die if defined $packagemap{$p};
186 $packagemap{$p} = $subdir;
188 foreach my $subdir (@{ $nail->{subdirs} }) {
189 my $gotpackage = read_manifest($subdir);
190 if (!defined $gotpackage) {
192 "$self: warning: ignoring subdir $subdir which has no Cargo.toml\n";
195 $packagemap{$gotpackage} //= $subdir;
200 foreach my $mf (keys %manifests) {
201 my $toml = $manifests{$mf};
202 foreach my $k (qw(dependencies build-dependencies dev-dependencies)) {
203 my $deps = $toml->{$k};
205 foreach my $p (keys %packagemap) {
206 my $info = $deps->{$p};
208 $info = { } unless ref $info;
209 delete $info->{version};
210 $info->{path} = $packagemap{$p};
213 my $nailing = "$mf.nailing~";
214 unlink $nailing or $!==ENOENT or die "$self: remove old $nailing: $!\n";
215 open N, '>', $nailing or die "$self: create new $nailing: $!\n";
216 print N to_toml($toml) or die "$self: write new $nailing: $!\n";
217 close N or die "$self: close new $nailing: $!\n";
224 if ($want_uninstall) {
226 foreach my $mf (keys %manifests) {
227 eval { uninstall1($mf, 0); 1; } or warn "$@";
233 foreach my $mf (keys %manifests) {
234 link "$mf", "$mf.unnailed" or $!==EEXIST
235 or die "$self: make backup link $mf.unnailed: $!\n";
240 foreach my $mf (keys %manifests) {
241 my $nailing = "$mf.nailing~";
242 my $nailed = "$mf.nailed~"; $nailed =~ s{/([^/]+)$}{/.$1} or die;
245 if (open NN, '<', $nailed) {
246 $diff = compare($nailing, \*NN);
247 die "$self: compare $nailing and $nailed: $!" if $diff<0;
249 $!==ENOENT or die "$self: check previous $nailed: $!\n";
259 rename $use, $mf or die "$self: install nailed $use: $!\n";
260 unlink $rm or $!==ENOENT or die "$self: remove old $rm: $!\n";
261 print STDERR "Nailed $mf\n" if $verbose>=2;
266 my $r = system @ARGV;
270 print STDERR "$self: could not execute $ARGV[0]: $!\n";
272 } elsif ($r & 0xff00) {
273 print STDERR "$self: $ARGV[0] failed (exit status $r)\n";
276 print STDERR "$self: $ARGV[0] died due to signal! (wait status $r)\n";
281 sub uninstall1 ($$) {
282 my ($mf, $enoentok) = @_;
283 my $unnailed = "$mf.unnailed";
284 rename $unnailed, $mf or ($enoentok && $!==ENOENT)
285 or die "$self: failed to revert: rename $unnailed back to $mf: $!\n";
289 foreach my $mf (keys %manifests) {
294 while (@ARGV && $ARGV[0] =~ m/^-/) {
301 } elsif (s{^-n}{-}) {
303 } elsif (s{^-D}{-}) {
306 die "$self: unknown short option(s) $_\n";
310 die "$self: unknown long option $_\n";
322 print STDERR Dumper(\%manifests, \%packagemap);
332 my $estatus = invoke();