lix/scripts/nix-channel.in

248 lines
8.6 KiB
Plaintext
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#! @perl@ -w @perlFlags@
use utf8;
use strict;
use File::Basename;
use File::Path qw(mkpath);
use Nix::Config;
use Nix::Manifest;
use File::Temp qw(tempdir);
binmode STDERR, ":encoding(utf8)";
Nix::Config::readConfig;
my $manifestDir = $Nix::Config::manifestDir;
# Turn on caching in nix-prefetch-url.
my $channelCache = "$Nix::Config::stateDir/channel-cache";
mkdir $channelCache, 0755 unless -e $channelCache;
$ENV{'NIX_DOWNLOAD_CACHE'} = $channelCache if -W $channelCache;
# Figure out the name of the `.nix-channels' file to use.
my $home = $ENV{"HOME"} or die '$HOME not set\n';
my $channelsList = "$home/.nix-channels";
my $nixDefExpr = "$home/.nix-defexpr";
# Figure out the name of the channels profile.
my $userName = getpwuid($<) || $ENV{"USER"} or die "cannot figure out user name";
my $profile = "$Nix::Config::stateDir/profiles/per-user/$userName/channels";
mkpath(dirname $profile, 0, 0755);
my %channels;
# Reads the list of channels.
sub readChannels {
return if (!-f $channelsList);
open CHANNELS, "<$channelsList" or die "cannot open $channelsList: $!";
while (<CHANNELS>) {
chomp;
next if /^\s*\#/;
my ($url, $name) = split ' ', $_;
$url =~ s/\/*$//; # remove trailing slashes
$name = basename $url unless defined $name;
$channels{$name} = $url;
}
close CHANNELS;
}
# Writes the list of channels.
sub writeChannels {
open CHANNELS, ">$channelsList" or die "cannot open $channelsList: $!";
foreach my $name (keys %channels) {
print CHANNELS "$channels{$name} $name\n";
}
close CHANNELS;
}
# Adds a channel.
sub addChannel {
my ($url, $name) = @_;
die "invalid channel URL $url" unless $url =~ /^(file|http|https):\/\//;
die "invalid channel identifier $name" unless $name =~ /^[a-zA-Z0-9_][a-zA-Z0-9_\-\.]*$/;
readChannels;
$channels{$name} = $url;
writeChannels;
}
# Remove a channel.
sub removeChannel {
my ($name) = @_;
readChannels;
my $url = $channels{$name};
deleteOldManifests($url . "/MANIFEST", undef) if defined $url;
delete $channels{$name};
writeChannels;
system("$Nix::Config::binDir/nix-env --profile '$profile' -e '$name'") == 0
or die "cannot remove channel $name\n";
}
# Fetch Nix expressions and pull manifests from the subscribed
# channels.
sub update {
my @channelNames = @_;
readChannels;
# Download each channel.
my $exprs = "";
foreach my $name (keys %channels) {
next if scalar @channelNames > 0 && ! grep { $_ eq $name } @{channelNames};
my $url = $channels{$name};
my $origUrl = "$url/MANIFEST";
# We want to download the url to a file to see if it's a tarball while also checking if we
# got redirected in the process, so that we can grab the various parts of a nix channel
# definition from a consistent location if the redirect changes mid-download.
my $tmpdir = tempdir( CLEANUP => 1 );
my $filename;
($url, $filename) = `cd $tmpdir && $Nix::Config::curl --silent --write-out '%{url_effective}\n%{filename_effective}' -L '$url' -O`;
die "$0: unable to check $url\n" if $? != 0;
chomp $url;
# If the URL contains a version number, append it to the name
# attribute (so that "nix-env -q" on the channels profile
# shows something useful).
my $cname = $name;
$cname .= $1 if basename($url) =~ /(-\d.*)$/;
my $path;
my $ret = -1;
if (-e "$tmpdir/$filename" && $filename =~ /\.tar\.(gz|bz2|xz)$/) {
# Get our temporary download into the store.
(my $hash, $path) = `PRINT_PATH=1 QUIET=1 $Nix::Config::binDir/nix-prefetch-url 'file://$tmpdir/$filename'`;
chomp $path;
# Try unpacking the expressions to see if they'll be valid for us to process later.
# Like anything in nix, this will cache the result so we don't do it again outside of the loop below.
$ret = system("$Nix::Config::binDir/nix-build --no-out-link -E 'import <nix/unpack-channel.nix> " .
"{ name = \"$cname\"; channelName = \"$name\"; src = builtins.storePath \"$path\"; }'");
}
# The URL doesn't unpack directly, so let's try treating it like a full channel folder with files in it
my $extraAttrs = "";
if ($ret != 0) {
# Check if the channel advertises a binary cache.
my $binaryCacheURL = `$Nix::Config::curl --silent '$url'/binary-cache-url`;
my $getManifest = ($Nix::Config::config{"force-manifest"} // "false") eq "true";
if ($? == 0 && $binaryCacheURL ne "") {
$extraAttrs .= "binaryCacheURL = \"$binaryCacheURL\"; ";
deleteOldManifests($origUrl, undef);
} else {
$getManifest = 1;
}
if ($getManifest) {
# No binary cache, so pull the channel manifest.
mkdir $manifestDir, 0755 unless -e $manifestDir;
die "$0: you do not have write permission to $manifestDir!\n" unless -W $manifestDir;
$ENV{'NIX_ORIG_URL'} = $origUrl;
system("$Nix::Config::binDir/nix-pull", "--skip-wrong-store", "$url/MANIFEST") == 0
or die "cannot pull manifest from $url\n";
}
# Download the channel tarball.
my $fullURL = "$url/nixexprs.tar.xz";
system("$Nix::Config::curl --fail --silent --head '$fullURL' > /dev/null") == 0 or
$fullURL = "$url/nixexprs.tar.bz2";
print STDERR "downloading Nix expressions from $fullURL...\n";
(my $hash, $path) = `PRINT_PATH=1 QUIET=1 $Nix::Config::binDir/nix-prefetch-url '$fullURL'`;
die "cannot fetch $fullURL\n" if $? != 0;
chomp $path;
}
# Regardless of where it came from, add the expression representing this channel to accumulated expression
$exprs .= "'f: f { name = \"$cname\"; channelName = \"$name\"; src = builtins.storePath \"$path\"; $extraAttrs }' ";
}
# Unpack the channel tarballs into the Nix store and install them
# into the channels profile.
print STDERR "unpacking channels...\n";
system("$Nix::Config::binDir/nix-env --profile '$profile' " .
"-f '<nix/unpack-channel.nix>' -i -E $exprs --quiet") == 0
or die "cannot unpack the channels";
# Make the channels appear in nix-env.
unlink $nixDefExpr if -l $nixDefExpr; # old-skool ~/.nix-defexpr
mkdir $nixDefExpr or die "cannot create directory $nixDefExpr" if !-e $nixDefExpr;
my $channelLink = "$nixDefExpr/channels";
unlink $channelLink; # !!! not atomic
symlink($profile, $channelLink) or die "cannot symlink $channelLink to $profile";
}
die "$0: argument expected\n" if scalar @ARGV == 0;
while (scalar @ARGV) {
my $arg = shift @ARGV;
if ($arg eq "--add") {
die "$0: --add requires one or two arguments\n" if scalar @ARGV < 1 || scalar @ARGV > 2;
my $url = shift @ARGV;
my $name = shift @ARGV;
unless (defined $name) {
$name = basename $url;
$name =~ s/-unstable//;
$name =~ s/-stable//;
}
addChannel($url, $name);
last;
}
if ($arg eq "--remove") {
die "$0: --remove requires one argument\n" if scalar @ARGV != 1;
removeChannel(shift @ARGV);
last;
}
if ($arg eq "--list") {
die "$0: --list requires one argument\n" if scalar @ARGV != 0;
readChannels;
foreach my $name (keys %channels) {
print "$name $channels{$name}\n";
}
last;
}
elsif ($arg eq "--update") {
update(@ARGV);
last;
}
elsif ($arg eq "--rollback") {
die "$0: --rollback has at most one argument\n" if scalar @ARGV > 1;
my $generation = shift @ARGV;
my @args = ("$Nix::Config::binDir/nix-env", "--profile", $profile);
if (defined $generation) {
die "invalid channel generation number $generation" unless $generation =~ /^[0-9]+$/;
push @args, "--switch-generation", $generation;
} else {
push @args, "--rollback";
}
system(@args) == 0 or exit 1;
last;
}
elsif ($arg eq "--help") {
exec "man nix-channel" or die;
}
elsif ($arg eq "--version") {
print "nix-channel (Nix) $Nix::Config::version\n";
exit 0;
}
else {
die "unknown argument $arg; try --help\n";
}
}