nix-channel improvements

"nix-channel --add" now accepts a second argument: the channel name.
This allows channels to have a nicer name than (say) nixpkgs_unstable.
If no name is given, it defaults to the last component of the URL
(with "-unstable" or "-stable" removed).

Also, channels are now stored in a profile
(/nix/var/nix/profiles/per-user/$USER/channels).  One advantage of
this is that it allows rollbacks (e.g. if "nix-channel --update" gives
an undesirable update).
This commit is contained in:
Eelco Dolstra 2012-04-14 18:38:52 +02:00
parent 969a14599d
commit e855c7e2c9
6 changed files with 104 additions and 120 deletions

View file

@ -1,11 +1,11 @@
with import <nix/config.nix>; with import <nix/config.nix>;
{ system, inputs }: { name, src }:
derivation { derivation {
name = "channels"; system = builtins.currentSystem;
builder = shell; builder = shell;
args = [ "-e" ./unpack-channel.sh ]; args = [ "-e" ./unpack-channel.sh ];
inherit system inputs bzip2 tar tr; inherit name src bzip2 tar tr;
PATH = "${nixBinDir}:${coreutils}"; PATH = "${nixBinDir}:${coreutils}";
} }

View file

@ -1,30 +1,4 @@
mkdir $out mkdir $out
mkdir $out/tmp cd $out
cd $out/tmp $bzip2 -d < $src | $tar xf -
mv * $out/$name
inputs=($inputs)
for ((n = 0; n < ${#inputs[*]}; n += 2)); do
channelName=${inputs[n]}
channelTarball=${inputs[n+1]}
echo "unpacking channel $channelName"
$bzip2 -d < $channelTarball | $tar xf -
if test -e */channel-name; then
channelName="$(cat */channel-name)"
fi
nr=1
attrName=$(echo $channelName | $tr -- '- ' '__')
dirName=$attrName
while test -e ../$dirName; do
nr=$((nr+1))
dirName=$attrName-$nr
done
mv * ../$dirName # !!! hacky
done
cd ..
rmdir tmp

View file

@ -19,7 +19,7 @@
<cmdsynopsis> <cmdsynopsis>
<command>nix-channel</command> <command>nix-channel</command>
<group choice='req'> <group choice='req'>
<arg choice='plain'><option>--add</option> <replaceable>url</replaceable></arg> <arg choice='plain'><option>--add</option> <replaceable>url</replaceable> <arg choice='opt'><replaceable>name</replaceable></arg></arg>
<arg choice='plain'><option>--remove</option> <replaceable>url</replaceable></arg> <arg choice='plain'><option>--remove</option> <replaceable>url</replaceable></arg>
<arg choice='plain'><option>--list</option></arg> <arg choice='plain'><option>--list</option></arg>
<arg choice='plain'><option>--update</option></arg> <arg choice='plain'><option>--update</option></arg>
@ -31,32 +31,39 @@
<para>A Nix channel is mechanism that allows you to automatically stay <para>A Nix channel is mechanism that allows you to automatically stay
up-to-date with a set of pre-built Nix expressions. A Nix channel is up-to-date with a set of pre-built Nix expressions. A Nix channel is
just a URL that points to a place that contains a set of Nix just a URL that points to a place containing a set of Nix expressions
expressions, as well as a <command>nix-push</command> manifest. See and a <command>nix-push</command> manifest. <phrase
also <xref linkend="sec-channels" />.</para> condition="manual">See also <xref linkend="sec-channels"
/>.</phrase></para>
<para>This command has the following operations: <para>This command has the following operations:
<variablelist> <variablelist>
<varlistentry><term><option>--add</option> <replaceable>url</replaceable></term> <varlistentry><term><option>--add</option> <replaceable>url</replaceable> [<replaceable>name</replaceable>]</term>
<listitem><para>Adds <replaceable>url</replaceable> to the list of <listitem><para>Adds a channel named
subscribed channels.</para></listitem> <replaceable>name</replaceable> with URL
<replaceable>url</replaceable> to the list of subscribed channels.
If <replaceable>name</replaceable> is omitted, it defaults to the
last component of <replaceable>url</replaceable>, with the
suffixes <literal>-stable</literal> or
<literal>-unstable</literal> removed.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry><term><option>--remove</option> <replaceable>url</replaceable></term> <varlistentry><term><option>--remove</option> <replaceable>name</replaceable></term>
<listitem><para>Removes <replaceable>url</replaceable> from the <listitem><para>Removes the channel named
list of subscribed channels.</para></listitem> <replaceable>name</replaceable> from the list of subscribed
channels.</para></listitem>
</varlistentry> </varlistentry>
<varlistentry><term><option>--list</option></term> <varlistentry><term><option>--list</option></term>
<listitem><para>Prints the URLs of all subscribed channels on <listitem><para>Prints the names and URLs of all subscribed
standard output.</para></listitem> channels on standard output.</para></listitem>
</varlistentry> </varlistentry>
@ -64,7 +71,7 @@ also <xref linkend="sec-channels" />.</para>
<listitem><para>Downloads the Nix expressions of all subscribed <listitem><para>Downloads the Nix expressions of all subscribed
channels, makes them the default for <command>nix-env</command> channels, makes them the default for <command>nix-env</command>
operations (by symlinking them in the directory operations (by symlinking them from the directory
<filename>~/.nix-defexpr</filename>), and performs a <filename>~/.nix-defexpr</filename>), and performs a
<command>nix-pull</command> on the manifests of all channels to <command>nix-pull</command> on the manifests of all channels to
make pre-built binaries available.</para></listitem> make pre-built binaries available.</para></listitem>
@ -75,8 +82,8 @@ also <xref linkend="sec-channels" />.</para>
</para> </para>
<para>Note that <option>--add</option> and <option>--remove</option> <para>Note that <option>--add</option> does not automatically perform
do not automatically perform an update.</para> an update.</para>
<para>The list of subscribed channels is stored in <para>The list of subscribed channels is stored in
<filename>~/.nix-channels</filename>.</para> <filename>~/.nix-channels</filename>.</para>
@ -90,4 +97,15 @@ respectively.</para>
</refsection> </refsection>
<refsection><title>Examples</title>
<para>To subscribe to the Nixpkgs channel and install the GNU Hello package:</para>
<screen>
$ nix-channel --add http://nixos.org/releases/nixpkgs/channels/nixpkgs-unstable
$ nix-channel --update
$ nix-env -iA nixpkgs.hello</screen>
</refsection>
</refentry> </refentry>

View file

@ -58,8 +58,9 @@ options.</phrase></para>
<listitem><para>Causes the result of a realisation <listitem><para>Causes the result of a realisation
(<option>--realise</option> and <option>--force-realise</option>) (<option>--realise</option> and <option>--force-realise</option>)
to be registered as a root of the garbage collector (see <xref to be registered as a root of the garbage collector<phrase
linkend="ssec-gc-roots" />). The root is stored in condition="manual"> (see <xref linkend="ssec-gc-roots"
/>)</phrase>. The root is stored in
<replaceable>path</replaceable>, which must be inside a directory <replaceable>path</replaceable>, which must be inside a directory
that is scanned for roots by the garbage collector (i.e., that is scanned for roots by the garbage collector (i.e.,
typically in a subdirectory of typically in a subdirectory of

View file

@ -1,6 +1,8 @@
#! @perl@ -w @perlFlags@ #! @perl@ -w @perlFlags@
use strict; use strict;
use File::Basename;
use File::Path qw(make_path);
use Nix::Config; use Nix::Config;
my $manifestDir = $Nix::Config::manifestDir; my $manifestDir = $Nix::Config::manifestDir;
@ -11,67 +13,67 @@ my $channelCache = "$Nix::Config::stateDir/channel-cache";
mkdir $channelCache, 0755 unless -e $channelCache; mkdir $channelCache, 0755 unless -e $channelCache;
$ENV{'NIX_DOWNLOAD_CACHE'} = $channelCache if -W $channelCache; $ENV{'NIX_DOWNLOAD_CACHE'} = $channelCache if -W $channelCache;
# Figure out the name of the `.nix-channels' file to use. # Figure out the name of the `.nix-channels' file to use.
my $home = $ENV{"HOME"}; my $home = $ENV{"HOME"} or die '$HOME not set\n';
die '$HOME not set' unless defined $home;
my $channelsList = "$home/.nix-channels"; my $channelsList = "$home/.nix-channels";
my $nixDefExpr = "$home/.nix-defexpr"; my $nixDefExpr = "$home/.nix-defexpr";
# Figure out the name of the channels profile.
my $userName = getpwuid($<) or die "cannot figure out user name";
my $profile = "$Nix::Config::stateDir/profiles/per-user/$userName/channels";
make_path(dirname $profile, mode => 0755);
my %channels;
my @channels;
# Reads the list of channels from the file $channelsList; # Reads the list of channels.
sub readChannels { sub readChannels {
return if (!-f $channelsList); return if (!-f $channelsList);
open CHANNELS, "<$channelsList" or die "cannot open `$channelsList': $!"; open CHANNELS, "<$channelsList" or die "cannot open `$channelsList': $!";
while (<CHANNELS>) { while (<CHANNELS>) {
chomp; chomp;
next if /^\s*\#/; next if /^\s*\#/;
push @channels, $_; my ($url, $name) = split ' ', $_;
$url =~ s/\/*$//; # remove trailing slashes
$name = basename $url unless defined $name;
$channels{$name} = $url;
} }
close CHANNELS; close CHANNELS;
} }
# Writes the list of channels to the file $channelsList; # Writes the list of channels.
sub writeChannels { sub writeChannels {
open CHANNELS, ">$channelsList" or die "cannot open `$channelsList': $!"; open CHANNELS, ">$channelsList" or die "cannot open `$channelsList': $!";
foreach my $url (@channels) { foreach my $name (keys %channels) {
print CHANNELS "$url\n"; print CHANNELS "$channels{$name} $name\n";
} }
close CHANNELS; close CHANNELS;
} }
# Adds a channel to the file $channelsList; # Adds a channel.
sub addChannel { sub addChannel {
my $url = shift; my ($url, $name) = @_;
readChannels; readChannels;
foreach my $url2 (@channels) { $channels{$name} = $url;
return if $url eq $url2;
}
push @channels, $url;
writeChannels; writeChannels;
} }
# Remove a channel from the file $channelsList; # Remove a channel.
sub removeChannel { sub removeChannel {
my $url = shift; my ($name) = @_;
my @left = ();
readChannels; readChannels;
foreach my $url2 (@channels) { delete $channels{$name};
push @left, $url2 if $url ne $url2;
}
@channels = @left;
writeChannels; writeChannels;
system("$Nix::Config::binDir/nix-env --profile '$profile' -e '$name'") == 0
or die "cannot remove channel `$name'";
} }
# Fetch Nix expressions and pull cache manifests from the subscribed # Fetch Nix expressions and pull manifests from the subscribed
# channels. # channels.
sub update { sub update {
readChannels; readChannels;
@ -82,64 +84,46 @@ sub update {
# Do we have write permission to the manifests directory? # Do we have write permission to the manifests directory?
die "$0: you do not have write permission to `$manifestDir'!\n" unless -W $manifestDir; die "$0: you do not have write permission to `$manifestDir'!\n" unless -W $manifestDir;
# Pull cache manifests. # Download each channel.
foreach my $url (@channels) { my $exprs = "";
#print "pulling cache manifest from `$url'\n"; foreach my $name (keys %channels) {
my $url = $channels{$name};
# Pull the channel manifest.
system("$Nix::Config::binDir/nix-pull", "--skip-wrong-store", "$url/MANIFEST") == 0 system("$Nix::Config::binDir/nix-pull", "--skip-wrong-store", "$url/MANIFEST") == 0
or die "cannot pull cache manifest from `$url'"; or die "cannot pull manifest from `$url'\n";
}
# Create a Nix expression that fetches and unpacks the channel Nix
# expressions.
my $inputs = "[";
foreach my $url (@channels) {
$url =~ /\/([^\/]+)\/?$/;
my $channelName = $1;
$channelName = "unnamed" unless defined $channelName;
# Download the channel tarball.
my $fullURL = "$url/nixexprs.tar.bz2"; my $fullURL = "$url/nixexprs.tar.bz2";
print "downloading Nix expressions from `$fullURL'...\n"; print STDERR "downloading Nix expressions from `$fullURL'...\n";
$ENV{"PRINT_PATH"} = 1; my ($hash, $path) = `PRINT_PATH=1 QUIET=1 $Nix::Config::binDir/nix-prefetch-url '$fullURL'`;
$ENV{"QUIET"} = 1;
my ($hash, $path) = `$Nix::Config::binDir/nix-prefetch-url '$fullURL'`;
die "cannot fetch `$fullURL'" if $? != 0; die "cannot fetch `$fullURL'" if $? != 0;
chomp $path; chomp $path;
$inputs .= '"' . $channelName . '"' . " " . $path . " ";
$exprs .= "'f: f { name = \"$name\"; src = builtins.storePath \"$path\"; }' ";
} }
$inputs .= "]";
# Figure out a name for the GC root. # Unpack the channel tarballs into the Nix store and install them
my $userName = getpwuid($<); # into the channels profile.
die "who ARE you? go away" unless defined $userName; print STDERR "unpacking channels...\n";
system("$Nix::Config::binDir/nix-env --profile '$profile' " .
my $rootFile = "$Nix::Config::stateDir/gcroots/per-user/$userName/channels"; "-f '<nix/unpack-channel.nix>' -i -E $exprs --quiet") == 0
or die "cannot unpack the channels";
# Build the Nix expression.
print "unpacking channel Nix expressions...\n";
my $outPath = `\\
$Nix::Config::binDir/nix-build --out-link '$rootFile' --drv-link '$rootFile'.tmp \\
'<nix/unpack-channel.nix>' \\
--argstr system @system@ --arg inputs '$inputs'`
or die "cannot unpack the channels";
chomp $outPath;
unlink "$rootFile.tmp";
# Make the channels appear in nix-env. # Make the channels appear in nix-env.
unlink $nixDefExpr if -l $nixDefExpr; # old-skool ~/.nix-defexpr unlink $nixDefExpr if -l $nixDefExpr; # old-skool ~/.nix-defexpr
mkdir $nixDefExpr or die "cannot create directory `$nixDefExpr'" if !-e $nixDefExpr; mkdir $nixDefExpr or die "cannot create directory `$nixDefExpr'" if !-e $nixDefExpr;
my $channelLink = "$nixDefExpr/channels"; my $channelLink = "$nixDefExpr/channels";
unlink $channelLink; # !!! not atomic unlink $channelLink; # !!! not atomic
symlink($outPath, $channelLink) or die "cannot symlink `$channelLink' to `$outPath'"; symlink($profile, $channelLink) or die "cannot symlink `$channelLink' to `$profile'";
} }
sub usageError { sub usageError {
print STDERR <<EOF; print STDERR <<EOF;
Usage: Usage:
nix-channel --add URL nix-channel --add URL [CHANNEL-NAME]
nix-channel --remove URL nix-channel --remove CHANNEL-NAME
nix-channel --list nix-channel --list
nix-channel --update nix-channel --update
EOF EOF
@ -154,22 +138,29 @@ while (scalar @ARGV) {
my $arg = shift @ARGV; my $arg = shift @ARGV;
if ($arg eq "--add") { if ($arg eq "--add") {
usageError if scalar @ARGV != 1; usageError if scalar @ARGV < 1 || scalar @ARGV > 2;
addChannel (shift @ARGV); my $url = shift @ARGV;
my $name = shift @ARGV;
unless (defined $name) {
$name = basename $url;
$name =~ s/-unstable//;
$name =~ s/-stable//;
}
addChannel($url, $name);
last; last;
} }
if ($arg eq "--remove") { if ($arg eq "--remove") {
usageError if scalar @ARGV != 1; usageError if scalar @ARGV != 1;
removeChannel (shift @ARGV); removeChannel(shift @ARGV);
last; last;
} }
if ($arg eq "--list") { if ($arg eq "--list") {
usageError if scalar @ARGV != 0; usageError if scalar @ARGV != 0;
readChannels; readChannels;
foreach my $url (@channels) { foreach my $name (keys %channels) {
print "$url\n"; print "$name $channels{$name}\n";
} }
last; last;
} }

View file

@ -9,9 +9,9 @@ rm -f $TEST_ROOT/.nix-channels
export HOME=$TEST_ROOT export HOME=$TEST_ROOT
# Test add/list/remove. # Test add/list/remove.
nix-channel --add http://foo/bar nix-channel --add http://foo/bar xyzzy
nix-channel --list | grep -q http://foo/bar nix-channel --list | grep -q http://foo/bar
nix-channel --remove http://foo/bar nix-channel --remove xyzzy
[ -e $TEST_ROOT/.nix-channels ] [ -e $TEST_ROOT/.nix-channels ]
[ "$(cat $TEST_ROOT/.nix-channels)" = '' ] [ "$(cat $TEST_ROOT/.nix-channels)" = '' ]