X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=nailing-cargo;h=80260d92a4946f66289bf67c9e3e854c40c72483;hb=92b6218a40919bdb8f149361faffc2589ba923a8;hp=fc70286b56fb7468de46571c8a03920c8c83ffa7;hpb=6485142577dc1e5a9c049461be6302e1c8e1bb2c;p=nailing-cargo.git diff --git a/nailing-cargo b/nailing-cargo index fc70286..80260d9 100755 --- a/nailing-cargo +++ b/nailing-cargo @@ -1,43 +1,6 @@ #!/usr/bin/perl -w - -# nailing-cargo: wrapper to use unpublished local crates -# -# Copyright (C) 2019-2020 Ian Jackson -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . - -# example usages: -# ../nailing-cargo/nailing-caretwgo make -# ../nailing-cargo/nailing-cargo cargo build -# CARGO='../nailing-cargo/nailing-cargo cargo' make - -# Why do we need this ? -# -# https://github.com/rust-lang/cargo/issues/6713 -# https://stackoverflow.com/questions/33025887/how-to-use-a-local-unpublished-crate -# https://github.com/rust-lang/cargo/issues/1481 - -# Needs libtoml-perl - -#: Cargo.nail: -# -# [packages] -# package = subdir -# package = { subdir = ... } -# -# [subdirs] -# subdir +# nailing-cargo: wrapper to use unpublished local crates +# SPDX-License-Identifier: AGPL-3.0-or-later our $self; @@ -46,7 +9,7 @@ use POSIX; use Types::Serialiser; our %archmap = ( -# RPI => 'arm-unknown-linux-gnueabihf', xxx + RPI => 'arm-unknown-linux-gnueabihf', ); BEGIN { @@ -82,6 +45,9 @@ our $lockfile = "../.nailing-cargo.lock"; our $cargo_lock_update; our $cargo_manifest_args; +our $cargo_target_arg=1; +our $alt_cargo_lock; +our $online; our @configs; our $verbose=1; @@ -99,9 +65,17 @@ sub read_or_enoent ($) { $r; } +sub stat_exists ($$) { + my ($fn, $what) = @_; + if (stat $fn) { return 1; } + $!==ENOENT or die "$self: stat $what: $fn: $!\n"; + return 0; +} + sub toml_or_enoent ($$) { my ($f,$what) = @_; my $toml = read_or_enoent($f) // return; + print STDERR "Read TOML from $f\n" if $dump; my ($v,$e) = from_toml($toml); if (!defined $v) { chomp $e; @@ -118,23 +92,20 @@ sub load1config ($) { } sub loadconfigs () { - my $cfgleaf = ".nailing-cargo-cfg.toml"; + my $dotfile = ".nailing-cargo.toml"; + load1config("../Nailing-Cargo.toml"); + load1config($dotfile); + load1config("$ENV{HOME}/$dotfile") if defined $ENV{HOME}; load1config("/etc/nailing-cargo/cfg.toml"); - load1config("$worksphere/$cfgleaf"); - load1config("$ENV{HOME}/$cfgleaf") if defined $ENV{HOME}; -} - -sub getcfg ($$) { - my ($k, $def) = @_; - foreach my $cfg (@configs) { - my $v = $cfg->{$k}; - return $v if defined $v; - } - return $def; } sub unlink_or_enoent ($) { unlink $_[0] or $!==ENOENT; } +sub same_file ($$) { + my ($x,$y) = @_; + "@$x[0..5]" eq "@$y[0..5]"; +} + sub takelock () { for (;;) { open LOCK, ">", $lockfile or die "$self: open/create $lockfile: $!\n"; @@ -145,7 +116,7 @@ sub takelock () { next if $! == ENOENT; die "$self: stat $lockfile: $!\n"; } - last if "@fstat[0..5]" eq "@stat[0..5]"; + last if same_file(\@fstat,\@stat); } } sub unlock () { @@ -161,16 +132,19 @@ sub badcfg { } sub cfg_uc { - my $v = $nail; - foreach my $k (@_) { - last unless defined $v; - ref($v) eq 'HASH' or badcfg @_, "parent key \`$k' is not a hash"; - $v = $v->{$k}; + foreach my $cfg (@configs) { + my $v = $cfg; + foreach my $k (@_) { + last unless defined $v; + ref($v) eq 'HASH' or badcfg @_, "parent key \`$k' is not a hash"; + $v = $v->{$k}; + } + return $v if defined $v; } - return $v; + return undef; } -sub cfg { +sub cfge { my $exp = shift @_; my $v = cfg_uc @_; my $got = ref($v) || 'scalar'; @@ -181,14 +155,20 @@ sub cfg { sub cfgn { my $exp = shift @_; - (cfg $exp, @_) // badcfg @_, "missing"; + (cfge $exp, @_) // badcfg @_, "missing"; } -sub cfgs { cfg 'scalar', @_ } +sub cfgs { cfge 'scalar', @_ } sub cfgsn { cfgn 'scalar', @_ } +sub cfg_bool { + my $v = cfg_uc @_; + return $v if !defined($v) || Types::Serialiser::is_bool $v; + badcfg @_, "expected boolean"; +} + sub cfgn_list { - my $l = cfg 'ARRAY', @_; + my $l = cfge 'ARRAY', @_; foreach my $x (@$l) { !ref $x or badcfg @_, "list contains non-scalar element"; } @@ -225,6 +205,8 @@ sub readnail () { } die "$e ?" if length $e; + $nail->{subdirs} //= [ ]; + if (!ref $nail->{subdirs}) { $nail->{subdirs} = [ grep /^[^\#]/, @@ -233,6 +215,42 @@ sub readnail () { $nail->{subdirs} ]; } + + unshift @configs, $nail; +} + +our @alt_cargo_lock_stat; + +sub consider_alt_cargo_lock () { + my @ck = qw(alt_cargo_lock); + # User should *either* have Cargo.lock in .gitignore, + # or expect to commit Cargo.lock.example ($alt_cargo_lock) + + $alt_cargo_lock = (cfg_uc @ck); + + my $force = 0; + if (defined($alt_cargo_lock) && ref($alt_cargo_lock) eq 'HASH') { + $force = cfg_bool qw(alt_cargo_lock force); + my @ck = qw(alt_cargo_lock file); + $alt_cargo_lock = cfg_uc @ck; + } + $alt_cargo_lock //= Types::Serialiser::true; + + if (Types::Serialiser::is_bool $alt_cargo_lock) { + if (!$alt_cargo_lock) { $alt_cargo_lock = undef; return; } + $alt_cargo_lock = 'Cargo.lock.example'; + } + + if (ref($alt_cargo_lock) || $alt_cargo_lock =~ m{/}) { + badcfg @ck, "expected boolean, or leafname"; + } + + if (!stat_exists $alt_cargo_lock, "alt_cargo_lock") { + $alt_cargo_lock = undef unless $force; + return; + } + + @alt_cargo_lock_stat = stat _; } our $oot_dir; # oot.dir or "Build" @@ -241,7 +259,9 @@ sub consider_oot () { $oot_dir = cfgs qw(oot dir); my $use = cfgs qw(oot use); unless (defined($oot_dir) || defined($use)) { - $cargo_lock_update//=0; + die "$self: specified --cargo-lock-update but not out-of-tree build!\n" + if $cargo_lock_update; + $cargo_lock_update=0; return; } $oot_dir //= 'Build'; @@ -322,28 +342,40 @@ sub calculate () { } sub addargs () { + $online //= cfg_bool qw(misc online); + if (@ARGV>=2 && - $ARGV[0] =~ m{\bcargo\b} && - $ARGV[1] =~ m/generate-lockfile|update/) { - $cargo_lock_update //= 1; - } else { - $cargo_lock_update //= 0; + $ARGV[0] =~ m{\bcargo\b}) { + if ($ARGV[1] =~ m/^(?:generate-lockfile|update)$/) { + $cargo_lock_update //= 1; + $target = undef; + } + if ($ARGV[1] =~ m/^(?:fetch)$/) { + $cargo_target_arg=0; + $online //= 1; + } } + $cargo_lock_update //= 0; $cargo_manifest_args //= (defined $oot_dir) && !$cargo_lock_update; + $online //= 0; + if ($cargo_manifest_args) { push @ARGV, "--manifest-path=${src_absdir}/Cargo.toml", - qw(--locked --target-dir=target); + qw(--locked); + push @ARGV, qw(--target-dir=target) if $cargo_target_arg; } if (defined $target) { if ($target =~ m{^[A-Z]}) { - $target = (cfg 'arch', $target) // $archmap{$target} + $target = (cfgs 'arch', $target) // $archmap{$target} // die "$self: --target=$target alias specified; not in cfg or map\n"; } push @ARGV, "--target=$target"; } + + push @ARGV, "--offline" unless $online; } our $oot_absdir; @@ -360,21 +392,29 @@ sub oot_massage_cmdline () { my @xargs; if (!$cargo_lock_update) { push @xargs, $build_absdir; - ($pre, $post) = ('cd "$1"; shift;', ''); + ($pre, $post) = ('cd "$1"; shift; ', ''); } else { push @xargs, $oot_absdir, $subdir, $src_absdir; - ($pre, $post) = (<<'END', <<'END'); + $pre = <<'END'; cd "$1"; shift; mkdir -p -- "$1"; cd "$1"; shift; - cp -- "$1"/Cargo.toml "$1"/Cargo.lock .; shift; - mkdir -p src; >src/lib.rs; -END - rm -r src Cargo.toml; + cp -- "$1"/Cargo.toml END - $pre =~ s/^\s+//mg; $pre =~ s/^\s+\n/ /g; - $post =~ s/^\s+//mg; $post =~ s/^\s+\n/ /g; + $pre .= <<'ENDLK' if stat_exists 'Cargo.lock', 'working cargo lockfile'; + "$1"/Cargo.lock +ENDLK + $pre .= <<'ENDCP'; + .; +ENDCP + $pre .= <<'ENDPRE'; + shift; + mkdir -p src; >src/lib.rs; >build.rs +ENDPRE + $post = <<'ENDPOST'; + rm -r src Cargo.toml build.rs; +ENDPOST } - my $addpath = (cfg qw(oot path_add)) // + my $addpath = (cfg_uc qw(oot path_add)) // $use eq 'really' ? Types::Serialiser::true : Types::Serialiser::false; $addpath = !Types::Serialiser::is_bool $addpath ? $addpath : @@ -386,15 +426,17 @@ END export PATH; END } + $pre =~ s/^\s+//mg; $pre =~ s/\s+/ /g; + $post =~ s/^\s+//mg; $post =~ s/\s+/ /g; my $getuser = sub { cfgsn qw(oot user) }; my @command; my $xe = $verbose >= 2 ? 'xe' : 'e'; my $sh_ec = sub { if (!length $post) { - @command = (@_, 'sh',"-${xe}c",$pre.' exec "$@"','--',@xargs); + @command = (@_, 'sh',"-${xe}c",$pre.'exec "$@"','--',@xargs); } else { - @command = (@_, 'sh',"-${xe}c",$pre.' "$@"; '.$post,'--',@xargs); + @command = (@_, 'sh',"-${xe}c",$pre.'"$@"; '.$post,'--',@xargs); } push @command, @ARGV; }; @@ -427,6 +469,8 @@ END my @c = cfgn_list qw(oot command); $command_sh->(@c); print STDERR "$self: out-of-tree, ssh'ish command: @c\n" if $verbose; + } elsif ($use eq 'null') { + $sh_ec->(); } else { die "$self: oot.use mode $use not recognised\n"; } @@ -434,6 +478,14 @@ END @ARGV = @command; } +sub setenvs () { + $ENV{NAILINGCARGO_WORKSPHERE} = $worksphere; + $ENV{NAILINGCARGO_MANIFEST_DIR} = $src_absdir; + $ENV{NAILINGCARGO_BUILDSPHERE} = $oot_absdir; + delete $ENV{NAILINGCARGO_BUILDSPHERE} unless $oot_absdir; + $ENV{NAILINGCARGO_BUILD_DIR} = $build_absdir // $src_absdir; +} + our $want_uninstall; END { @@ -442,14 +494,42 @@ END { foreach my $mf (keys %manifests) { eval { uninstall1($mf,1); 1; } or warn "$@"; } + eval { unaltcargolock(1); 1; } or warn "$@"; } } +our $cleanup_cargo_lock; sub makebackups () { foreach my $mf (keys %manifests) { link "$mf", "$mf.unnailed" or $!==EEXIST or die "$self: make backup link $mf.unnailed: $!\n"; } + + if (defined($alt_cargo_lock)) { + if (@alt_cargo_lock_stat) { + print STDERR "$self: using alt_cargo_lock `$alt_cargo_lock'..." + if $verbose>=3; + if (link $alt_cargo_lock, 'Cargo.lock') { + print STDERR " linked\n" if $verbose>=3; + } elsif ($! != EEXIST) { + print STDERR "\n" if $verbose>=3; + die "$self: make \`Cargo.lock' available as \`$alt_cargo_lock': $!\n"; + } else { + print STDERR "checking quality." if $verbose>=3; + my @lock_stat = stat 'Cargo.lock' + or die "$self: stat Cargo.lock (for alt check: $!\n"; + same_file(\@alt_cargo_lock_stat, \@lock_stat) + or die +"$self: \`Cargo.lock' and alt file \`$alt_cargo_lock' both exist and are not the same file!\n"; + } + $cleanup_cargo_lock = 1; + } else { + $cleanup_cargo_lock = 1; + # If Cargo.lock exists and alt doesn't, that means either + # that a previous run was interrupted, or that the user has + # messed up. + } + } } sub nailed ($) { @@ -459,7 +539,18 @@ sub nailed ($) { } sub install () { + my @our_unfound_stab = stat_exists('Cargo.toml', 'local Cargo.toml') + ? (stat _) : (); foreach my $mf (keys %manifests) { + if (@our_unfound_stab) { + if (stat_exists $mf, "manifest in to-be-nailed directory") { + my @mf_stab = stat _ ; + if ("@mf_stab[0..1]" eq "@our_unfound_stab[0..1]") { + @our_unfound_stab = (); + } + } + } + my $nailing = "$mf.nailing~"; my $nailed = nailed($mf); my ($use, $rm); @@ -482,6 +573,11 @@ sub install () { unlink_or_enoent $rm or die "$self: remove old $rm: $!\n"; print STDERR "$self: nailed $mf\n" if $verbose>=3; } + + if (@our_unfound_stab) { + print STDERR + "$self: *WARNING* cwd is not in Cargo.nail thbough it has Cargo.toml!\n"; + } } sub invoke () { @@ -501,12 +597,13 @@ sub invoke () { } sub cargo_lock_update_after () { - return unless $cargo_lock_update; - # avoids importing File::Copy and the error handling is about as good - $!=0; $?=0; - my $r= system qw(cp --), "$build_absdir/Cargo.lock", "Cargo.lock"; - die "$self: run cp: $! $?" if $r<0 || $r & 0xff; - die "$self: failed to update local Cargo.lock (wait status $r)\n" if $r; + if ($cargo_lock_update) { + # avoids importing File::Copy and the error handling is about as good + $!=0; $?=0; + my $r= system qw(cp --), "$build_absdir/Cargo.lock", "Cargo.lock"; + die "$self: run cp: $! $?" if $r<0 || $r & 0xff; + die "$self: failed to update local Cargo.lock (wait status $r)\n" if $r; + } } sub uninstall1 ($$) { @@ -516,12 +613,29 @@ sub uninstall1 ($$) { or die "$self: failed to restore: rename $unnailed back to $mf: $!\n"; } +sub unaltcargolock ($) { + my ($enoentok) = @_; + return unless $cleanup_cargo_lock; + die 'internal error!' unless defined $alt_cargo_lock; + + # we ignore $enoentok because we don't know if one was supposed to + # have been created. + + rename('Cargo.lock', $alt_cargo_lock) or $!==ENOENT or die + "$self: cleanup: rename possibly-updated \`Cargo.lock' to \`$alt_cargo_lock': $!\n"; + + unlink 'Cargo.lock' or $!==ENOENT or die + "$self: cleanup: remove \`Cargo.lock' in favour of \`$alt_cargo_lock': $!\n"; + # ^ this also helps clean up the stupid rename() corner case +} + sub uninstall () { foreach my $mf (keys %manifests) { my $nailed = nailed($mf); link $mf, $nailed or die "$self: preserve (link) $mf as $nailed: $!\n"; uninstall1($mf,0); } + unaltcargolock(0); } while (@ARGV && $ARGV[0] =~ m/^-/) { @@ -537,20 +651,30 @@ while (@ARGV && $ARGV[0] =~ m/^-/) { $noact++; } elsif (s{^-D}{-}) { $dump++; - } elsif (s{^-T(.+)}{-}s) { + } elsif (s{^-A(.+)}{-}s) { $target = $1; } elsif (s{^-([uU])}{-}) { - $cargo_lock_update= $1=~m/[A-Z]/; + $cargo_lock_update= $1=~m/[a-z]/; } elsif (s{^-([mM])}{-}) { - $cargo_manifest_args= $1=~m/[A-Z]/; + $cargo_manifest_args= $1=~m/[a-z]/; + } elsif (s{^-([tT])}{-}) { + $cargo_target_arg= $1=~m/[a-z]/; + } elsif (s{^-([oO])}{-}) { + $online= $1=~m/[a-z]/; } else { die "$self: unknown short option(s) $_\n"; } } - } elsif (s{^--target=}{}) { + } elsif (s{^--(?:target|arch)=}{}) { $target = $_; - } elsif (m{^--no-cargo-lock-update}) { - $cargo_lock_update=0; + } elsif (m{^--(no-)?cargo-lock-update}) { + $cargo_lock_update= !!$1; + } elsif (m{^--(no-)?cargo-manifest-args}) { + $cargo_manifest_args= !!$1; + } elsif (m{^--(no-)?cargo-target-dir-arg}) { + $cargo_target_arg= !!$1; + } elsif (m{^--(on|off)line$}) { + $online = $1 eq 'on'; } else { die "$self: unknown long option $_\n"; } @@ -558,12 +682,15 @@ while (@ARGV && $ARGV[0] =~ m/^-/) { die "$self: need command to run\n" unless @ARGV || $noact; +loadconfigs(); takelock(); readnail(); +consider_alt_cargo_lock(); consider_oot(); readorigs(); calculate(); addargs(); +our @display_cmd = @ARGV; oot_massage_cmdline(); setenvs(); @@ -587,17 +714,19 @@ $want_uninstall = 1; makebackups(); install(); -printf STDERR "$self: nailed (%s manifests, %s packages)\n", - (scalar keys %manifests), (scalar keys %packagemap) +printf STDERR "$self: nailed (%s manifests, %s packages)%s\n", + (scalar keys %manifests), (scalar keys %packagemap), + (defined($alt_cargo_lock) and ", using `$alt_cargo_lock'") if $verbose; +print STDERR "$self: invoking: @display_cmd\n" if $verbose; my $estatus = invoke(); -uninstall(); -$want_uninstall = 1; - cargo_lock_update_after(); +uninstall(); +$want_uninstall = 0; + print STDERR "$self: unnailed. status $estatus.\n" if $verbose; exit $estatus;