#! @perl@ -w @perlFlags@ use strict; use File::Basename; use File::Path qw(mkpath); use Nix::Config; use Nix::Manifest; 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($<) 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) = @_; 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"; # Check if $url is a redirect. If so, follow it now to ensure # consistency if the redirection is changed between # downloading the manifest and the tarball. my $headers = `$Nix::Config::curl --silent --head '$url'`; die "$0: unable to check `$url'\n" if $? != 0; $headers =~ s/\r//g; $url = $1 if $headers =~ /^Location:\s*(.*)\s*$/m; # Check if the channel advertises a binary cache. my $binaryCacheURL = `$Nix::Config::curl --silent '$url'/binary-cache-url`; my $extraAttrs = ""; 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.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; # 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.*)$/; $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 "--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"; } }