X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?a=blobdiff_plain;f=nailing-cargo;h=80260d92a4946f66289bf67c9e3e854c40c72483;hb=92b6218a40919bdb8f149361faffc2589ba923a8;hp=0997cdc1e2ddb401560fb4ec5aebb8c3a39f8ca7;hpb=06676f48146a175d5ff860729f565f8f3937d111;p=nailing-cargo.git diff --git a/nailing-cargo b/nailing-cargo index 0997cdc..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; @@ -45,6 +8,10 @@ use strict; use POSIX; use Types::Serialiser; +our %archmap = ( + RPI => 'arm-unknown-linux-gnueabihf', +); + BEGIN { $self = $0; $self =~ s{^.*/(?=.)}{}; my $deref = $0; @@ -75,11 +42,17 @@ $worksphere =~ s{/([^/]+)$}{} our $subdir = $1; # leafname our $lockfile = "../.nailing-cargo.lock"; -our $oot_cargo_lock_faff; + +our $cargo_lock_update; +our $cargo_manifest_args; +our $cargo_target_arg=1; +our $alt_cargo_lock; +our $online; our @configs; our $verbose=1; our ($noact,$dump); +our $target; sub read_or_enoent ($) { my ($fn) = @_; @@ -92,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; @@ -111,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"; @@ -138,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 () { @@ -154,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'; @@ -174,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"; } @@ -218,6 +205,8 @@ sub readnail () { } die "$e ?" if length $e; + $nail->{subdirs} //= [ ]; + if (!ref $nail->{subdirs}) { $nail->{subdirs} = [ grep /^[^\#]/, @@ -226,6 +215,56 @@ 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" + +sub consider_oot () { + $oot_dir = cfgs qw(oot dir); + my $use = cfgs qw(oot use); + unless (defined($oot_dir) || defined($use)) { + 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'; } our %manifests; @@ -302,45 +341,80 @@ sub calculate () { } } -our @out_command; +sub addargs () { + $online //= cfg_bool qw(misc online); -our $oot_dir; # oot.dir or "Build" -our $oot_absdir; + if (@ARGV>=2 && + $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; -our $build_absdir; # .../Build/ + $online //= 0; -sub calculate_oot () { - $oot_dir = cfgs qw(oot dir); - my $use = cfgs qw(oot use); - return unless defined($oot_dir) || defined($use); - $oot_dir //= 'Build'; + if ($cargo_manifest_args) { + push @ARGV, "--manifest-path=${src_absdir}/Cargo.toml", + qw(--locked); + push @ARGV, qw(--target-dir=target) if $cargo_target_arg; + } - if (@ARGV && $ARGV[0] =~ m/generate-lockfile|update/) { - $oot_cargo_lock_faff = 1; + if (defined $target) { + if ($target =~ m{^[A-Z]}) { + $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; +our $build_absdir; # .../Build/ + +sub oot_massage_cmdline () { + return unless defined $oot_dir; + + my $use = cfgs qw(oot use); $oot_absdir = ($oot_dir !~ m{^/} ? "$worksphere/" : ""). $oot_dir; $build_absdir = "$oot_absdir/$subdir"; my ($pre,$post); my @xargs; - if (!$oot_cargo_lock_faff) { + if (!$cargo_lock_update) { push @xargs, $build_absdir; - ($pre, $post) = ('cd "$1"; shift;', ''); + ($pre, $post) = ('cd "$1"; shift; ', ''); } else { - push @xargs, $build_absdir, $subdir, $src_absdir; - ($pre, $post) = (<<'END', <<'END'); + push @xargs, $oot_absdir, $subdir, $src_absdir; + $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 : @@ -352,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; }; @@ -372,19 +448,29 @@ END } @ARGV; @command = @_, "set -${xe}; $pre $quoted; $post"; }; + print STDERR "$self: out-of-tree, building in: \`$build_absdir'\n" + if $verbose; if ($use eq 'really') { my $user = $getuser->(); my @pw = getpwnam $user or die "$self: oot.user \`$user' lookup failed\n"; my $homedir = $pw[7]; $sh_ec->('really','-u',$user,'env',"HOME=$homedir"); + print STDERR "$self: using really to run as user \`$user'\n" if $verbose; } elsif ($use eq 'ssh') { my $user = $getuser->(); $user .= '@localhost' unless $user =~ m/\@/; $command_sh->('ssh',$user); - } elsif ($use eq 'command_sh') { - $command_sh->(cfgn_list qw(oot command)); + print STDERR "$self: using ssh to run as \`$user'\n" if $verbose; } elsif ($use eq 'command_args') { - $sh_ec->(cfgn_list qw(oot command)) + my @c = cfgn_list qw(oot command); + $sh_ec->(@c); + print STDERR "$self: out-of-tree, adverbial command: @c\n" if $verbose; + } elsif ($use eq 'command_sh') { + 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"; } @@ -392,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 { @@ -400,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 ($) { @@ -417,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); @@ -440,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 () { @@ -458,6 +596,16 @@ sub invoke () { } } +sub cargo_lock_update_after () { + 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 ($$) { my ($mf, $enoentok) = @_; my $unnailed = "$mf.unnailed"; @@ -465,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/^-/) { @@ -486,12 +651,30 @@ while (@ARGV && $ARGV[0] =~ m/^-/) { $noact++; } elsif (s{^-D}{-}) { $dump++; - } elsif (s{^-L}{-}) { - $oot_cargo_lock_faff=1; + } elsif (s{^-A(.+)}{-}s) { + $target = $1; + } elsif (s{^-([uU])}{-}) { + $cargo_lock_update= $1=~m/[a-z]/; + } elsif (s{^-([mM])}{-}) { + $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|arch)=}{}) { + $target = $_; + } 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"; } @@ -499,16 +682,29 @@ 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(); -calculate_oot(); +addargs(); +our @display_cmd = @ARGV; +oot_massage_cmdline(); +setenvs(); if ($dump) { eval ' use Data::Dumper; - print STDERR Dumper(\%manifests, \%packagemap, \@ARGV); + print STDERR Dumper(\%manifests) if $dump>=2; + print STDERR Dumper(\%packagemap, \@ARGV, + { src_absdir => $src_absdir, + worksphere => $worksphere, + subdir => $subdir, + oot_dir => $oot_dir, + oot_absdir => $oot_absdir, + build_absdir => $build_absdir }); ' or die $@; } @@ -518,16 +714,18 @@ $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(); -get_cargo_lock() if $oot_cargo_lock_faff; +uninstall(); +$want_uninstall = 0; print STDERR "$self: unnailed. status $estatus.\n" if $verbose;