1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-27 08:31:16 +02:00

Integrate perl with the other meson builds

One big dev shell!
This commit is contained in:
John Ericson 2024-06-04 18:10:25 -04:00
parent e0b4691754
commit a83d95e26e
20 changed files with 52 additions and 27 deletions

1
src/perl/.version Symbolic link
View file

@ -0,0 +1 @@
../../.version

2
src/perl/.yath.rc.in Normal file
View file

@ -0,0 +1,2 @@
[test]
-I=rel(@lib_dir@)

7
src/perl/MANIFEST Normal file
View file

@ -0,0 +1,7 @@
Changes
Makefile.PL
MANIFEST
Nix.xs
README
t/Nix.t
lib/Nix.pm

View file

@ -0,0 +1,25 @@
package Nix::Config;
use MIME::Base64;
use Nix::Store;
$version = "@PACKAGE_VERSION@";
$binDir = Nix::Store::getBinDir;
$storeDir = Nix::Store::getStoreDir;
%config = ();
sub readConfig {
my $config = "$confDir/nix.conf";
return unless -f $config;
open CONFIG, "<$config" or die "cannot open '$config'";
while (<CONFIG>) {
/^\s*([\w\-\.]+)\s*=\s*(.*)$/ or next;
$config{$1} = $2;
}
close CONFIG;
}
return 1;

View file

@ -0,0 +1,61 @@
package Nix::CopyClosure;
use utf8;
use strict;
use Nix::Config;
use Nix::Store;
use Nix::SSH;
use List::Util qw(sum);
use IPC::Open2;
sub copyToOpen {
my ($from, $to, $sshHost, $storePaths, $includeOutputs, $dryRun, $useSubstitutes) = @_;
$useSubstitutes = 0 if $dryRun || !defined $useSubstitutes;
# Get the closure of this path.
my @closure = reverse(topoSortPaths(computeFSClosure(0, $includeOutputs,
map { followLinksToStorePath $_ } @{$storePaths})));
# Send the "query valid paths" command with the "lock" option
# enabled. This prevents a race where the remote host
# garbage-collect paths that are already there. Optionally, ask
# the remote host to substitute missing paths.
syswrite($to, pack("L<x4L<x4L<x4", 1, 1, $useSubstitutes)) or die;
writeStrings(\@closure, $to);
# Get back the set of paths that are already valid on the remote host.
my %present;
$present{$_} = 1 foreach readStrings($from);
my @missing = grep { !$present{$_} } @closure;
return if !@missing;
my $missingSize = 0;
$missingSize += (queryPathInfo($_, 1))[3] foreach @missing;
printf STDERR "copying %d missing paths (%.2f MiB) to '$sshHost'...\n",
scalar(@missing), $missingSize / (1024**2);
return if $dryRun;
# Send the "import paths" command.
syswrite($to, pack("L<x4", 4)) or die;
exportPaths(fileno($to), @missing);
readInt($from) == 1 or die "remote machine '$sshHost' failed to import closure\n";
}
sub copyTo {
my ($sshHost, $storePaths, $includeOutputs, $dryRun, $useSubstitutes) = @_;
# Connect to the remote host.
my ($from, $to) = connectToRemoteNix($sshHost, []);
copyToOpen($from, $to, $sshHost, $storePaths, $includeOutputs, $dryRun, $useSubstitutes);
close $to;
}
1;

View file

@ -0,0 +1,325 @@
package Nix::Manifest;
use utf8;
use strict;
use DBI;
use DBD::SQLite;
use Cwd;
use File::stat;
use File::Path;
use Fcntl ':flock';
use MIME::Base64;
use Nix::Config;
use Nix::Store;
our @ISA = qw(Exporter);
our @EXPORT = qw(readManifest writeManifest addPatch parseNARInfo fingerprintPath);
sub addNAR {
my ($narFiles, $storePath, $info) = @_;
$$narFiles{$storePath} = []
unless defined $$narFiles{$storePath};
my $narFileList = $$narFiles{$storePath};
my $found = 0;
foreach my $narFile (@{$narFileList}) {
$found = 1 if $narFile->{url} eq $info->{url};
}
push @{$narFileList}, $info if !$found;
}
sub addPatch {
my ($patches, $storePath, $patch) = @_;
$$patches{$storePath} = []
unless defined $$patches{$storePath};
my $patchList = $$patches{$storePath};
my $found = 0;
foreach my $patch2 (@{$patchList}) {
$found = 1 if
$patch2->{url} eq $patch->{url} &&
$patch2->{basePath} eq $patch->{basePath};
}
push @{$patchList}, $patch if !$found;
return !$found;
}
sub readManifest_ {
my ($manifest, $addNAR, $addPatch) = @_;
# Decompress the manifest if necessary.
if ($manifest =~ /\.bz2$/) {
open MANIFEST, "$Nix::Config::bzip2 -d < $manifest |"
or die "cannot decompress '$manifest': $!";
} else {
open MANIFEST, "<$manifest"
or die "cannot open '$manifest': $!";
}
my $inside = 0;
my $type;
my $manifestVersion = 2;
my ($storePath, $url, $hash, $size, $basePath, $baseHash, $patchType);
my ($narHash, $narSize, $references, $deriver, $copyFrom, $system, $compressionType);
while (<MANIFEST>) {
chomp;
s/\#.*$//g;
next if (/^$/);
if (!$inside) {
if (/^\s*(\w*)\s*\{$/) {
$type = $1;
$type = "narfile" if $type eq "";
$inside = 1;
undef $storePath;
undef $url;
undef $hash;
undef $size;
undef $narHash;
undef $narSize;
undef $basePath;
undef $baseHash;
undef $patchType;
undef $system;
$references = "";
$deriver = "";
$compressionType = "bzip2";
}
} else {
if (/^\}$/) {
$inside = 0;
if ($type eq "narfile") {
&$addNAR($storePath,
{ url => $url, hash => $hash, size => $size
, narHash => $narHash, narSize => $narSize
, references => $references
, deriver => $deriver
, system => $system
, compressionType => $compressionType
});
}
elsif ($type eq "patch") {
&$addPatch($storePath,
{ url => $url, hash => $hash, size => $size
, basePath => $basePath, baseHash => $baseHash
, narHash => $narHash, narSize => $narSize
, patchType => $patchType
});
}
}
elsif (/^\s*StorePath:\s*(\/\S+)\s*$/) { $storePath = $1; }
elsif (/^\s*CopyFrom:\s*(\/\S+)\s*$/) { $copyFrom = $1; }
elsif (/^\s*Hash:\s*(\S+)\s*$/) { $hash = $1; }
elsif (/^\s*URL:\s*(\S+)\s*$/) { $url = $1; }
elsif (/^\s*Compression:\s*(\S+)\s*$/) { $compressionType = $1; }
elsif (/^\s*Size:\s*(\d+)\s*$/) { $size = $1; }
elsif (/^\s*BasePath:\s*(\/\S+)\s*$/) { $basePath = $1; }
elsif (/^\s*BaseHash:\s*(\S+)\s*$/) { $baseHash = $1; }
elsif (/^\s*Type:\s*(\S+)\s*$/) { $patchType = $1; }
elsif (/^\s*NarHash:\s*(\S+)\s*$/) { $narHash = $1; }
elsif (/^\s*NarSize:\s*(\d+)\s*$/) { $narSize = $1; }
elsif (/^\s*References:\s*(.*)\s*$/) { $references = $1; }
elsif (/^\s*Deriver:\s*(\S+)\s*$/) { $deriver = $1; }
elsif (/^\s*ManifestVersion:\s*(\d+)\s*$/) { $manifestVersion = $1; }
elsif (/^\s*System:\s*(\S+)\s*$/) { $system = $1; }
# Compatibility;
elsif (/^\s*NarURL:\s*(\S+)\s*$/) { $url = $1; }
elsif (/^\s*MD5:\s*(\S+)\s*$/) { $hash = "md5:$1"; }
}
}
close MANIFEST;
return $manifestVersion;
}
sub readManifest {
my ($manifest, $narFiles, $patches) = @_;
readManifest_($manifest,
sub { addNAR($narFiles, @_); },
sub { addPatch($patches, @_); } );
}
sub writeManifest {
my ($manifest, $narFiles, $patches, $noCompress) = @_;
open MANIFEST, ">$manifest.tmp"; # !!! check exclusive
print MANIFEST "version {\n";
print MANIFEST " ManifestVersion: 3\n";
print MANIFEST "}\n";
foreach my $storePath (sort (keys %{$narFiles})) {
my $narFileList = $$narFiles{$storePath};
foreach my $narFile (@{$narFileList}) {
print MANIFEST "{\n";
print MANIFEST " StorePath: $storePath\n";
print MANIFEST " NarURL: $narFile->{url}\n";
print MANIFEST " Compression: $narFile->{compressionType}\n";
print MANIFEST " Hash: $narFile->{hash}\n" if defined $narFile->{hash};
print MANIFEST " Size: $narFile->{size}\n" if defined $narFile->{size};
print MANIFEST " NarHash: $narFile->{narHash}\n";
print MANIFEST " NarSize: $narFile->{narSize}\n" if $narFile->{narSize};
print MANIFEST " References: $narFile->{references}\n"
if defined $narFile->{references} && $narFile->{references} ne "";
print MANIFEST " Deriver: $narFile->{deriver}\n"
if defined $narFile->{deriver} && $narFile->{deriver} ne "";
print MANIFEST " System: $narFile->{system}\n" if defined $narFile->{system};
print MANIFEST "}\n";
}
}
foreach my $storePath (sort (keys %{$patches})) {
my $patchList = $$patches{$storePath};
foreach my $patch (@{$patchList}) {
print MANIFEST "patch {\n";
print MANIFEST " StorePath: $storePath\n";
print MANIFEST " NarURL: $patch->{url}\n";
print MANIFEST " Hash: $patch->{hash}\n";
print MANIFEST " Size: $patch->{size}\n";
print MANIFEST " NarHash: $patch->{narHash}\n";
print MANIFEST " NarSize: $patch->{narSize}\n" if $patch->{narSize};
print MANIFEST " BasePath: $patch->{basePath}\n";
print MANIFEST " BaseHash: $patch->{baseHash}\n";
print MANIFEST " Type: $patch->{patchType}\n";
print MANIFEST "}\n";
}
}
close MANIFEST;
rename("$manifest.tmp", $manifest)
or die "cannot rename $manifest.tmp: $!";
# Create a bzipped manifest.
unless (defined $noCompress) {
system("$Nix::Config::bzip2 < $manifest > $manifest.bz2.tmp") == 0
or die "cannot compress manifest";
rename("$manifest.bz2.tmp", "$manifest.bz2")
or die "cannot rename $manifest.bz2.tmp: $!";
}
}
# Return a fingerprint of a store path to be used in binary cache
# signatures. It contains the store path, the base-32 SHA-256 hash of
# the contents of the path, and the references.
sub fingerprintPath {
my ($storePath, $narHash, $narSize, $references) = @_;
die if substr($storePath, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir;
die if substr($narHash, 0, 7) ne "sha256:";
# Convert hash from base-16 to base-32, if necessary.
$narHash = "sha256:" . convertHash("sha256", substr($narHash, 7), 1)
if length($narHash) == 71;
die if length($narHash) != 59;
foreach my $ref (@{$references}) {
die if substr($ref, 0, length($Nix::Config::storeDir)) ne $Nix::Config::storeDir;
}
return "1;" . $storePath . ";" . $narHash . ";" . $narSize . ";" . join(",", @{$references});
}
# Parse a NAR info file.
sub parseNARInfo {
my ($storePath, $content, $requireValidSig, $location) = @_;
my ($storePath2, $url, $fileHash, $fileSize, $narHash, $narSize, $deriver, $system, $sig);
my $compression = "bzip2";
my @refs;
foreach my $line (split "\n", $content) {
return undef unless $line =~ /^(.*): (.*)$/;
if ($1 eq "StorePath") { $storePath2 = $2; }
elsif ($1 eq "URL") { $url = $2; }
elsif ($1 eq "Compression") { $compression = $2; }
elsif ($1 eq "FileHash") { $fileHash = $2; }
elsif ($1 eq "FileSize") { $fileSize = int($2); }
elsif ($1 eq "NarHash") { $narHash = $2; }
elsif ($1 eq "NarSize") { $narSize = int($2); }
elsif ($1 eq "References") { @refs = split / /, $2; }
elsif ($1 eq "Deriver") { $deriver = $2; }
elsif ($1 eq "System") { $system = $2; }
elsif ($1 eq "Sig") { $sig = $2; }
}
return undef if $storePath ne $storePath2 || !defined $url || !defined $narHash;
my $res =
{ url => $url
, compression => $compression
, fileHash => $fileHash
, fileSize => $fileSize
, narHash => $narHash
, narSize => $narSize
, refs => [ @refs ]
, deriver => $deriver
, system => $system
};
if ($requireValidSig) {
# FIXME: might be useful to support multiple signatures per .narinfo.
if (!defined $sig) {
warn "NAR info file '$location' lacks a signature; ignoring\n";
return undef;
}
my ($keyName, $sig64) = split ":", $sig;
return undef unless defined $keyName && defined $sig64;
my $publicKey = $Nix::Config::binaryCachePublicKeys{$keyName};
if (!defined $publicKey) {
warn "NAR info file '$location' is signed by unknown key '$keyName'; ignoring\n";
return undef;
}
my $fingerprint;
eval {
$fingerprint = fingerprintPath(
$storePath, $narHash, $narSize,
[ map { "$Nix::Config::storeDir/$_" } @refs ]);
};
if ($@) {
warn "cannot compute fingerprint of '$location'; ignoring\n";
return undef;
}
if (!checkSignature($publicKey, decode_base64($sig64), $fingerprint)) {
warn "NAR info file '$location' has an incorrect signature; ignoring\n";
return undef;
}
$res->{signedBy} = $keyName;
}
return $res;
}
return 1;

110
src/perl/lib/Nix/SSH.pm Normal file
View file

@ -0,0 +1,110 @@
package Nix::SSH;
use utf8;
use strict;
use File::Temp qw(tempdir);
use IPC::Open2;
our @ISA = qw(Exporter);
our @EXPORT = qw(
@globalSshOpts
readN readInt readString readStrings
writeInt writeString writeStrings
connectToRemoteNix
);
our @globalSshOpts = split ' ', ($ENV{"NIX_SSHOPTS"} or "");
sub readN {
my ($bytes, $from) = @_;
my $res = "";
while ($bytes > 0) {
my $s;
my $n = sysread($from, $s, $bytes);
die "I/O error reading from remote side\n" if !defined $n;
die "got EOF while expecting $bytes bytes from remote side\n" if !$n;
$bytes -= $n;
$res .= $s;
}
return $res;
}
sub readInt {
my ($from) = @_;
return unpack("L<x4", readN(8, $from));
}
sub readString {
my ($from) = @_;
my $len = readInt($from);
my $s = readN($len, $from);
readN(8 - $len % 8, $from) if $len % 8; # skip padding
return $s;
}
sub readStrings {
my ($from) = @_;
my $n = readInt($from);
my @res;
push @res, readString($from) while $n--;
return @res;
}
sub writeInt {
my ($n, $to) = @_;
syswrite($to, pack("L<x4", $n)) or die;
}
sub writeString {
my ($s, $to) = @_;
my $len = length $s;
my $req .= pack("L<x4", $len);
$req .= $s;
$req .= "\000" x (8 - $len % 8) if $len % 8;
syswrite($to, $req) or die;
}
sub writeStrings {
my ($ss, $to) = @_;
writeInt(scalar(@{$ss}), $to);
writeString($_, $to) foreach @{$ss};
}
sub connectToRemoteNix {
my ($sshHost, $sshOpts, $extraFlags) = @_;
$extraFlags ||= "";
# Start nix-store --serve on the remote host.
my ($from, $to);
# FIXME: don't start a shell, start ssh directly.
my $pid = open2($from, $to, "exec ssh -x -a $sshHost @globalSshOpts @{$sshOpts} nix-store --serve --write $extraFlags");
# Do the handshake.
my $magic;
eval {
my $SERVE_MAGIC_1 = 0x390c9deb; # FIXME
my $clientVersion = 0x200;
syswrite($to, pack("L<x4L<x4", $SERVE_MAGIC_1, $clientVersion)) or die;
$magic = readInt($from);
};
die "unable to connect to '$sshHost'\n" if $@;
die "did not get valid handshake from remote host\n" if $magic != 0x5452eecb;
my $serverVersion = readInt($from);
die "unsupported server version\n" if $serverVersion < 0x200 || $serverVersion >= 0x300;
return ($from, $to, $pid);
}
1;

45
src/perl/lib/Nix/Store.pm Normal file
View file

@ -0,0 +1,45 @@
package Nix::Store;
use strict;
use warnings;
require Exporter;
our @ISA = qw(Exporter);
our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
StoreWrapper
StoreWrapper::new
StoreWrapper::isValidPath StoreWrapper::queryReferences StoreWrapper::queryPathInfo StoreWrapper::queryDeriver StoreWrapper::queryPathHash
StoreWrapper::queryPathFromHashPart
StoreWrapper::topoSortPaths StoreWrapper::computeFSClosure followLinksToStorePath StoreWrapper::exportPaths StoreWrapper::importPaths
StoreWrapper::addToStore StoreWrapper::makeFixedOutputPath
StoreWrapper::derivationFromPath
StoreWrapper::addTempRoot
StoreWrapper::queryRawRealisation
hashPath hashFile hashString convertHash
signString checkSignature
getBinDir getStoreDir
setVerbosity
);
our $VERSION = '0.15';
sub backtick {
open(RES, "-|", @_) or die;
local $/;
my $res = <RES> || "";
close RES or die;
return $res;
}
require XSLoader;
XSLoader::load('Nix::Store', $VERSION);
1;
__END__

434
src/perl/lib/Nix/Store.xs Normal file
View file

@ -0,0 +1,434 @@
#include "config-util.h"
#include "config-store.h"
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
/* Prevent a clash between some Perl and libstdc++ macros. */
#undef do_open
#undef do_close
#include "derivations.hh"
#include "realisation.hh"
#include "globals.hh"
#include "store-api.hh"
#include "posix-source-accessor.hh"
#include <sodium.h>
#include <nlohmann/json.hpp>
using namespace nix;
static bool libStoreInitialized = false;
struct StoreWrapper {
ref<Store> store;
};
MODULE = Nix::Store PACKAGE = Nix::Store
PROTOTYPES: ENABLE
TYPEMAP: <<HERE
StoreWrapper * O_OBJECT
OUTPUT
O_OBJECT
sv_setref_pv( $arg, CLASS, (void*)$var );
INPUT
O_OBJECT
if ( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) {
$var = ($type)SvIV((SV*)SvRV( $arg ));
}
else {
warn( \"${Package}::$func_name() -- \"
\"$var not a blessed SV reference\");
XSRETURN_UNDEF;
}
HERE
#undef dNOOP // Hack to work around "error: declaration of 'Perl___notused' has a different language linkage" error message on clang.
#define dNOOP
void
StoreWrapper::DESTROY()
StoreWrapper *
StoreWrapper::new(char * s = nullptr)
CODE:
static std::shared_ptr<Store> _store;
try {
if (!libStoreInitialized) {
initLibStore();
libStoreInitialized = true;
}
if (items == 1) {
_store = openStore();
RETVAL = new StoreWrapper {
.store = ref<Store>{_store}
};
} else {
RETVAL = new StoreWrapper {
.store = openStore(s)
};
}
} catch (Error & e) {
croak("%s", e.what());
}
OUTPUT:
RETVAL
void init()
CODE:
if (!libStoreInitialized) {
initLibStore();
libStoreInitialized = true;
}
void setVerbosity(int level)
CODE:
verbosity = (Verbosity) level;
int
StoreWrapper::isValidPath(char * path)
CODE:
try {
RETVAL = THIS->store->isValidPath(THIS->store->parseStorePath(path));
} catch (Error & e) {
croak("%s", e.what());
}
OUTPUT:
RETVAL
SV *
StoreWrapper::queryReferences(char * path)
PPCODE:
try {
for (auto & i : THIS->store->queryPathInfo(THIS->store->parseStorePath(path))->references)
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::queryPathHash(char * path)
PPCODE:
try {
auto s = THIS->store->queryPathInfo(THIS->store->parseStorePath(path))->narHash.to_string(HashFormat::Nix32, true);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::queryDeriver(char * path)
PPCODE:
try {
auto info = THIS->store->queryPathInfo(THIS->store->parseStorePath(path));
if (!info->deriver) XSRETURN_UNDEF;
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(*info->deriver).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::queryPathInfo(char * path, int base32)
PPCODE:
try {
auto info = THIS->store->queryPathInfo(THIS->store->parseStorePath(path));
if (!info->deriver)
XPUSHs(&PL_sv_undef);
else
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(*info->deriver).c_str(), 0)));
auto s = info->narHash.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, true);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
mXPUSHi(info->registrationTime);
mXPUSHi(info->narSize);
AV * refs = newAV();
for (auto & i : info->references)
av_push(refs, newSVpv(THIS->store->printStorePath(i).c_str(), 0));
XPUSHs(sv_2mortal(newRV((SV *) refs)));
AV * sigs = newAV();
for (auto & i : info->sigs)
av_push(sigs, newSVpv(i.c_str(), 0));
XPUSHs(sv_2mortal(newRV((SV *) sigs)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::queryRawRealisation(char * outputId)
PPCODE:
try {
auto realisation = THIS->store->queryRealisation(DrvOutput::parse(outputId));
if (realisation)
XPUSHs(sv_2mortal(newSVpv(realisation->toJSON().dump().c_str(), 0)));
else
XPUSHs(sv_2mortal(newSVpv("", 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::queryPathFromHashPart(char * hashPart)
PPCODE:
try {
auto path = THIS->store->queryPathFromHashPart(hashPart);
XPUSHs(sv_2mortal(newSVpv(path ? THIS->store->printStorePath(*path).c_str() : "", 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::computeFSClosure(int flipDirection, int includeOutputs, ...)
PPCODE:
try {
StorePathSet paths;
for (int n = 2; n < items; ++n)
THIS->store->computeFSClosure(THIS->store->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs);
for (auto & i : paths)
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::topoSortPaths(...)
PPCODE:
try {
StorePathSet paths;
for (int n = 0; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
auto sorted = THIS->store->topoSortPaths(paths);
for (auto & i : sorted)
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::followLinksToStorePath(char * path)
CODE:
try {
RETVAL = newSVpv(THIS->store->printStorePath(THIS->store->followLinksToStorePath(path)).c_str(), 0);
} catch (Error & e) {
croak("%s", e.what());
}
OUTPUT:
RETVAL
void
StoreWrapper::exportPaths(int fd, ...)
PPCODE:
try {
StorePathSet paths;
for (int n = 1; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
FdSink sink(fd);
THIS->store->exportPaths(paths, sink);
} catch (Error & e) {
croak("%s", e.what());
}
void
StoreWrapper::importPaths(int fd, int dontCheckSigs)
PPCODE:
try {
FdSource source(fd);
THIS->store->importPaths(source, dontCheckSigs ? NoCheckSigs : CheckSigs);
} catch (Error & e) {
croak("%s", e.what());
}
SV *
hashPath(char * algo, int base32, char * path)
PPCODE:
try {
Hash h = hashPath(
PosixSourceAccessor::createAtRoot(path),
FileIngestionMethod::Recursive, parseHashAlgo(algo)).first;
auto s = h.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, false);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * hashFile(char * algo, int base32, char * path)
PPCODE:
try {
Hash h = hashFile(parseHashAlgo(algo), path);
auto s = h.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, false);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * hashString(char * algo, int base32, char * s)
PPCODE:
try {
Hash h = hashString(parseHashAlgo(algo), s);
auto s = h.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, false);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * convertHash(char * algo, char * s, int toBase32)
PPCODE:
try {
auto h = Hash::parseAny(s, parseHashAlgo(algo));
auto s = h.to_string(toBase32 ? HashFormat::Nix32 : HashFormat::Base16, false);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * signString(char * secretKey_, char * msg)
PPCODE:
try {
auto sig = SecretKey(secretKey_).signDetached(msg);
XPUSHs(sv_2mortal(newSVpv(sig.c_str(), sig.size())));
} catch (Error & e) {
croak("%s", e.what());
}
int checkSignature(SV * publicKey_, SV * sig_, char * msg)
CODE:
try {
STRLEN publicKeyLen;
unsigned char * publicKey = (unsigned char *) SvPV(publicKey_, publicKeyLen);
if (publicKeyLen != crypto_sign_PUBLICKEYBYTES)
throw Error("public key is not valid");
STRLEN sigLen;
unsigned char * sig = (unsigned char *) SvPV(sig_, sigLen);
if (sigLen != crypto_sign_BYTES)
throw Error("signature is not valid");
RETVAL = crypto_sign_verify_detached(sig, (unsigned char *) msg, strlen(msg), publicKey) == 0;
} catch (Error & e) {
croak("%s", e.what());
}
OUTPUT:
RETVAL
SV *
StoreWrapper::addToStore(char * srcPath, int recursive, char * algo)
PPCODE:
try {
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
auto path = THIS->store->addToStore(
std::string(baseNameOf(srcPath)),
PosixSourceAccessor::createAtRoot(srcPath),
method, parseHashAlgo(algo));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(path).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::makeFixedOutputPath(int recursive, char * algo, char * hash, char * name)
PPCODE:
try {
auto h = Hash::parseAny(hash, parseHashAlgo(algo));
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
auto path = THIS->store->makeFixedOutputPath(name, FixedOutputInfo {
.method = method,
.hash = h,
.references = {},
});
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(path).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::derivationFromPath(char * drvPath)
PREINIT:
HV *hash;
CODE:
try {
Derivation drv = THIS->store->derivationFromPath(THIS->store->parseStorePath(drvPath));
hash = newHV();
HV * outputs = newHV();
for (auto & i : drv.outputsAndOptPaths(*THIS->store)) {
hv_store(
outputs, i.first.c_str(), i.first.size(),
!i.second.second
? newSV(0) /* null value */
: newSVpv(THIS->store->printStorePath(*i.second.second).c_str(), 0),
0);
}
hv_stores(hash, "outputs", newRV((SV *) outputs));
AV * inputDrvs = newAV();
for (auto & i : drv.inputDrvs.map)
av_push(inputDrvs, newSVpv(THIS->store->printStorePath(i.first).c_str(), 0)); // !!! ignores i->second
hv_stores(hash, "inputDrvs", newRV((SV *) inputDrvs));
AV * inputSrcs = newAV();
for (auto & i : drv.inputSrcs)
av_push(inputSrcs, newSVpv(THIS->store->printStorePath(i).c_str(), 0));
hv_stores(hash, "inputSrcs", newRV((SV *) inputSrcs));
hv_stores(hash, "platform", newSVpv(drv.platform.c_str(), 0));
hv_stores(hash, "builder", newSVpv(drv.builder.c_str(), 0));
AV * args = newAV();
for (auto & i : drv.args)
av_push(args, newSVpv(i.c_str(), 0));
hv_stores(hash, "args", newRV((SV *) args));
HV * env = newHV();
for (auto & i : drv.env)
hv_store(env, i.first.c_str(), i.first.size(), newSVpv(i.second.c_str(), 0), 0);
hv_stores(hash, "env", newRV((SV *) env));
RETVAL = newRV_noinc((SV *)hash);
} catch (Error & e) {
croak("%s", e.what());
}
OUTPUT:
RETVAL
void
StoreWrapper::addTempRoot(char * storePath)
PPCODE:
try {
THIS->store->addTempRoot(THIS->store->parseStorePath(storePath));
} catch (Error & e) {
croak("%s", e.what());
}
SV * getBinDir()
PPCODE:
XPUSHs(sv_2mortal(newSVpv(settings.nixBinDir.c_str(), 0)));
SV * getStoreDir()
PPCODE:
XPUSHs(sv_2mortal(newSVpv(settings.nixStore.c_str(), 0)));

47
src/perl/lib/Nix/Utils.pm Normal file
View file

@ -0,0 +1,47 @@
package Nix::Utils;
use utf8;
use File::Temp qw(tempdir);
our @ISA = qw(Exporter);
our @EXPORT = qw(checkURL uniq writeFile readFile mkTempDir);
$urlRE = "(?: [a-zA-Z][a-zA-Z0-9\+\-\.]*\:[a-zA-Z0-9\%\/\?\:\@\&\=\+\$\,\-\_\.\!\~\*]+ )";
sub checkURL {
my ($url) = @_;
die "invalid URL '$url'\n" unless $url =~ /^ $urlRE $ /x;
}
sub uniq {
my %seen;
my @res;
foreach my $name (@_) {
next if $seen{$name};
$seen{$name} = 1;
push @res, $name;
}
return @res;
}
sub writeFile {
my ($fn, $s) = @_;
open TMP, ">$fn" or die "cannot create file '$fn': $!";
print TMP "$s" or die;
close TMP or die;
}
sub readFile {
local $/ = undef;
my ($fn) = @_;
open TMP, "<$fn" or die "cannot open file '$fn': $!";
my $s = <TMP>;
close TMP or die;
return $s;
}
sub mkTempDir {
my ($name) = @_;
return tempdir("$name.XXXXXX", CLEANUP => 1, DIR => $ENV{"TMPDIR"} // $ENV{"XDG_RUNTIME_DIR"} // "/tmp")
|| die "cannot create a temporary directory";
}

View file

@ -0,0 +1,59 @@
# Nix-Perl Scripts
#============================================================================
# Sources
#-------------------------------------------------
nix_perl_store_xs = files('Store.xs')
nix_perl_scripts = files(
'CopyClosure.pm',
'Manifest.pm',
'SSH.pm',
'Store.pm',
'Utils.pm',
)
foreach f : nix_perl_scripts
fs.copyfile(f)
endforeach
# Targets
#---------------------------------------------------
nix_perl_scripts += configure_file(
output : 'Config.pm',
input : 'Config.pm.in',
configuration : nix_perl_conf,
)
nix_perl_store_cc = custom_target(
'Store.cc',
output : 'Store.cc',
input : nix_perl_store_xs,
command : [xsubpp, '@INPUT@', '-output', '@OUTPUT@'],
)
# Build Nix::Store Library
#-------------------------------------------------
nix_perl_store_lib = library(
'Store',
sources : nix_perl_store_cc,
name_prefix : '',
install : true,
install_mode : 'rwxr-xr-x',
install_dir : join_paths(nix_perl_install_dir, 'auto', 'Nix', 'Store'),
dependencies : nix_perl_store_dep_list,
)
# Install Scripts
#---------------------------------------------------
install_data(
nix_perl_scripts,
install_mode : 'rw-r--r--',
install_dir : join_paths(nix_perl_install_dir,'Nix'),
)

160
src/perl/meson.build Normal file
View file

@ -0,0 +1,160 @@
# Nix-Perl Meson build
#============================================================================
# init project
#============================================================================
project (
'nix-perl',
'cpp',
version : files('.version'),
meson_version : '>= 1.1',
license : 'LGPL-2.1-or-later',
)
# setup env
#-------------------------------------------------
fs = import('fs')
cpp = meson.get_compiler('cpp')
nix_perl_conf = configuration_data()
nix_perl_conf.set('PACKAGE_VERSION', meson.project_version())
# set error arguments
#-------------------------------------------------
error_args = [
'-Wno-pedantic',
'-Wno-non-virtual-dtor',
'-Wno-unused-parameter',
'-Wno-variadic-macros',
'-Wdeprecated-declarations',
'-Wno-missing-field-initializers',
'-Wno-unknown-warning-option',
'-Wno-unused-variable',
'-Wno-literal-suffix',
'-Wno-reserved-user-defined-literal',
'-Wno-duplicate-decl-specifier',
'-Wno-pointer-bool-conversion',
]
add_project_arguments(
cpp.get_supported_arguments(error_args),
language : 'cpp',
)
# set install directories
#-------------------------------------------------
prefix = get_option('prefix')
libdir = join_paths(prefix, get_option('libdir'))
# Dependencies
#============================================================================
# Required Programs
#-------------------------------------------------
xz = find_program('xz')
xsubpp = find_program('xsubpp')
perl = find_program('perl')
curl = find_program('curl')
yath = find_program('yath', required : false)
# Required Libraries
#-------------------------------------------------
bzip2_dep = dependency('bzip2')
curl_dep = dependency('libcurl')
libsodium_dep = dependency('libsodium')
nix_store_dep = dependency('nix-store')
# Finding Perl Headers is a pain. as they do not have
# pkgconfig available, are not in a standard location,
# and are installed into a version folder. Use the
# Perl binary to give hints about perl include dir.
#-------------------------------------------------
perl_archname = run_command(
perl, '-e', 'use Config; print $Config{archname};', check: true).stdout()
perl_version = run_command(
perl, '-e', 'use Config; print $Config{version};', check: true).stdout()
perl_archlibexp = run_command(
perl, '-e', 'use Config; print $Config{archlibexp};', check: true).stdout()
perl_site_libdir = run_command(
perl, '-e', 'use Config; print $Config{installsitearch};', check: true).stdout()
nix_perl_install_dir = join_paths(
libdir, 'perl5', 'site_perl', perl_version, perl_archname)
# print perl hints for logs
#-------------------------------------------------
message('Perl archname: @0@'.format(perl_archname))
message('Perl version: @0@'.format(perl_version))
message('Perl archlibexp: @0@'.format(perl_archlibexp))
message('Perl install site: @0@'.format(perl_site_libdir))
message('Assumed Nix-Perl install dir: @0@'.format(nix_perl_install_dir))
# Now find perl modules
#-------------------------------------------------
perl_check_dbi = run_command(
perl,
'-e', 'use DBI; use DBD::SQLite;',
'-I@0@'.format(get_option('dbi_path')),
'-I@0@'.format(get_option('dbd_sqlite_path')),
check: true
)
if perl_check_dbi.returncode() == 2
error('The Perl modules DBI and/or DBD::SQLite are missing.')
else
message('Found Perl Modules: DBI, DBD::SQLite.')
endif
# declare perl dependency
#-------------------------------------------------
perl_dep = declare_dependency(
dependencies : cpp.find_library(
'perl',
has_headers : [
join_paths(perl_archlibexp, 'CORE', 'perl.h'),
join_paths(perl_archlibexp, 'CORE', 'EXTERN.h')],
dirs : [
join_paths(perl_archlibexp, 'CORE'),
],
),
include_directories : join_paths(perl_archlibexp, 'CORE'),
)
# declare dependencies
#-------------------------------------------------
nix_perl_store_dep_list = [
perl_dep,
bzip2_dep,
curl_dep,
libsodium_dep,
nix_store_dep,
]
# # build
# #-------------------------------------------------
lib_dir = join_paths('lib', 'Nix')
subdir(lib_dir)
if get_option('tests').enabled()
yath_rc_conf = configuration_data()
yath_rc_conf.set('lib_dir', lib_dir)
yath_rc = configure_file(
output : '.yath.rc',
input : '.yath.rc.in',
configuration : yath_rc_conf,
)
subdir('t')
test(
'nix-perl-test',
yath,
args : ['test'],
workdir : meson.current_build_dir(),
depends : [nix_perl_store_lib],
)
endif

27
src/perl/meson.options Normal file
View file

@ -0,0 +1,27 @@
# Nix-Perl build options
#============================================================================
# compiler args
#============================================================================
option(
'tests',
type : 'feature',
value : 'disabled',
description : 'run nix-perl tests')
# Location of Perl Modules
#============================================================================
option(
'dbi_path',
type : 'string',
value : '/usr',
description : 'path to perl::dbi')
option(
'dbd_sqlite_path',
type : 'string',
value : '/usr',
description : 'path to perl::dbd-SQLite')

77
src/perl/package.nix Normal file
View file

@ -0,0 +1,77 @@
{ lib
, fileset
, stdenv
, perl
, perlPackages
, meson
, ninja
, pkg-config
, nix-store
, curl
, bzip2
, xz
, boost
, libsodium
, darwin
, versionSuffix ? ""
}:
perl.pkgs.toPerlModule (stdenv.mkDerivation (finalAttrs: {
pname = "nix-perl";
version = lib.fileContents ./.version + versionSuffix;
src = fileset.toSource {
root = ./.;
fileset = fileset.unions ([
./MANIFEST
./lib
./meson.build
./meson.options
] ++ lib.optionals finalAttrs.doCheck [
./.yath.rc.in
./t
]);
};
nativeBuildInputs = [
meson
ninja
pkg-config
];
buildInputs = [
nix-store
curl
bzip2
xz
perl
boost
]
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
++ lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.Security;
# `perlPackages.Test2Harness` is marked broken for Darwin
doCheck = !stdenv.isDarwin;
nativeCheckInputs = [
perlPackages.Test2Harness
];
preConfigure =
# "Inline" .version so its not a symlink, and includes the suffix
''
echo ${finalAttrs.version} > .version
'';
mesonFlags = [
(lib.mesonOption "dbi_path" "${perlPackages.DBI}/${perl.libPrefix}")
(lib.mesonOption "dbd_sqlite_path" "${perlPackages.DBDSQLite}/${perl.libPrefix}")
(lib.mesonEnable "tests" finalAttrs.doCheck)
];
mesonCheckFlags = [
"--print-errorlogs"
];
enableParallelBuilding = true;
}))

13
src/perl/t/init.t Normal file
View file

@ -0,0 +1,13 @@
use strict;
use warnings;
use Test2::V0;
use Nix::Store;
my $s = new Nix::Store("dummy://");
my $res = $s->isValidPath("/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar");
ok(!$res, "should not have path");
done_testing;

15
src/perl/t/meson.build Normal file
View file

@ -0,0 +1,15 @@
# Nix-Perl Tests
#============================================================================
# src
#---------------------------------------------------
nix_perl_tests = files(
'init.t',
)
foreach f : nix_perl_tests
fs.copyfile(f)
endforeach