aboutsummaryrefslogblamecommitdiff
path: root/src/bin/paku
blob: 262d0c8dec37de95694dd72493c45ecf4fb9da69 (plain) (tree)
1
2
3
4
5
6
7
8
9

                   






                                         


                      



                          
                                                


                         
 




                          

                                                                               

                                                 





                                                     

                                                                            
 
 

                         



                                                                   
 





                                                                         
                   

 







                              
 
 
         
                                             

                      

 



                      

 
 
                                






                                                               
                                              

 
                    




































                                                                                

 


                                    




                              





                                               
                                     


                                                               
 

                                                      
 












                                                                      
 


                                                            
                           






                                                   
                                     
                                      
                           
                 












                                                            


                                                                        




                                                   




                     

 


                                    
                     

                                             




                                                         
                     
                                                           






                                               
                      







                                                   
 

                                                      
 
                                                




                                                





                                                            









                                                                             
 






                                                                             
 









                                                                             

                                                     

                   
                                               
                                     




                                                         

                           






                              

 








                                                        
 
                         
 


                                                                                               
 




                                                     
 



                                                                                                
 
                           
 
 
                                                       
 



                                                                                                                   
 













































                                                                                                                                                
 
 

                           
 
 






                                          
 

                                              
 
 
                                                                                             
 

                                              
 
 

                                                                      
 

                                                                                    
 

                                                            
 

                                                                                                
 

                                                               
 
 



















                                                                
#!/usr/bin/env perl

use v5.34;
use warnings;
use feature 'signatures';
no warnings ('experimental::signatures');
use Getopt::Std ();
use JSON ();
use MIME::Base64 ();
use File::Basename ();
use Digest::MD5 ();
use Digest::SHA ();

sub usage($fh) {
	print $fh <<~'EOF'
		Usage:
		  paku [-d DIR] [-f FILE] ACTION
		  paku -h
		EOF
}

sub help($fh) {
	print $fh <<~'EOF'


		Options:
		  -d DIR        use DIR for intermediary data (default: .paku/)
		  -f FILE       use data from FILE (default: paku.lock)
		  -h, --help    show this message

		  ACTION        what to emit, one of:
		                - guix
		                - debian
		                - nix
		                - html


		Generate package definitions for different package managers.


		Examples:

		  Emit Guix package definitions using all defaults:

		    $ paku guix > packages.scm
		    $ guix build -f packages.scm


		  Emit Debian build recipes from "debian.json", using
		  ".debtmp/" as the temporary directory, and execute them
		  as is:

		    $ paku -d .debtemp/ -f debian.json debian | make -f-
		EOF
}


for (@ARGV) {
	last if $_ eq '--';
	if ($_ eq '--help') {
		usage *STDOUT;
		help *STDOUT;
		exit;
	}
}

my %opts;
if (!Getopt::Std::getopts('d:f:h', \%opts)) {
	usage *STDERR;
	exit 2;
}

if ($opts{h}) {
	usage *STDOUT;
	help *STDOUT;
	exit;
}


my $dir   = $opts{d} || '.paku';
my $fname = $opts{f} || 'paku.lock';
my $json_str = do {
	open my $fh, $fname or die "Failed opening \"$fname\"";
	local $/;
	<$fh>;
};
my $json = JSON::decode_json($json_str);
my $action = $ARGV[0] or die "Missing ACTION";


sub emit_release() {
	my $f = $ARGV[1];
	my $name = File::Basename::basename $f;
	my $size = (stat($f))[7];

	my $now = `env LANG=POSIX.UTF-8 date -u '+%a, %d %b %Y %H:%M:%S +0000'`;
	chomp $now;

	my $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $md5  = Digest::MD5->new->addfile($fh)->hexdigest;
	close $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $sha1 = Digest::SHA->new(1)->addfile($fh)->hexdigest;
	close $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $sha256 = Digest::SHA->new(256)->addfile($fh)->hexdigest;
	close $fh;

	open ($fh, '<', $f) or die "Can't open \"$f\": $!";
	my $sha512 = Digest::SHA->new(512)->addfile($fh)->hexdigest;
	close $fh;

	print <<~EOF;
		Date: $now
		MD5Sum:
		 $md5             $size $name
		SHA1:
		 $sha1             $size $name
		SHA256:
		 $sha256             $size $name
		SHA512:
		 $sha512             $size $name
		EOF
	exit;
}

sub emit_nix() {
	my $ns = $json->{namespace};
	$ns =~ s/\./-/g;
	print <<~EOF;
		{ pkgs }:
		self: super: {
		  $ns = rec {
		EOF
	for my $pkg (@{$json->{packages}}) {
		my $ver = $pkg->{version};
		$ver =~ s/^v//;
		$ver =~ s/\./-/g;
		if ($pkg->{type} eq 'latest') {
			$ver = 'latest';
			print <<~EOF;
			    $pkg->{name} = $pkg->{name}-latest;
			EOF
		}

		my $long = $pkg->{'long-description'};
		$long =~ s/^(.)/          $1/gm;

		print <<~EOF;
		    $pkg->{name}-$ver = pkgs.stdenv.mkDerivation rec {
		      name = "$pkg->{name}";
		      version = "$pkg->{version}";

		      src = fetchTarball {
		        url =
		          "$pkg->{url}";
		        sha256 = "$pkg->{sha256}";
		      };

		      nativeBuildInputs = with pkgs; [
		EOF

		for my $input (@{$pkg->{'native-inputs'}}) {
			print <<~EOF;
			        $input
			EOF
		}
		print <<~EOF;
		      ];

		      buildInputs = with pkgs; [
		EOF
		for my $input (@{$pkg->{inputs}}) {
			print <<~EOF;
			        $input
			EOF
		}
		print <<~EOF;
		      ];

		      makeFlags = [ "PREFIX=\$(out)" ];

		      doCheck = true;
		      enableParallelBuilding = true;

		      meta = with pkgs.lib; {
		        description = "$pkg->{description}";
		        longDescription = ''
		$long
		        '';
		        homepage = "$pkg->{'base-url'}/";
		        changelog = "$pkg->{'base-url'}/CHANGELOG.html";
		        downloadPage = "$pkg->{'base-url'}/#releases";
		        license = licenses.agpl3;
		        platforms = platforms.unix;
		      };
		    };
		EOF
	}
	print <<~EOF;
	  };
	}
	EOF
}

sub emit_guix() {
	my $ns = $json->{namespace};
	$ns =~ s/\./ /g;
	print <<~EOF;
		(define-module ($ns packages)
		EOF
	for my $module (@{$json->{guix}{'module-use'}}) {
		print <<~EOF;
		  #:use-module (gnu packages $module)
		EOF
	}
	print <<~EOF;
	  #:use-module ((guix licenses) #:prefix licenses:)
	  #:use-module (guix gexp)
	  #:use-module (guix packages)
	  #:use-module (guix download)
	  #:use-module (guix build-system gnu))

	EOF

	my @pkgs = ();
	for my $pkg (@{$json->{packages}}) {
		my $ver = $pkg->{version};
		$ver =~ s/^v//;
		$ver =~ s/\./-/g;
		if ($pkg->{type} eq 'latest') {
			push @pkgs, "$pkg->{name}";
			$ver = 'latest';
		}

		my $long = $pkg->{'long-description'};
		$long =~ s/^(.)/          $1/gm;

		push @pkgs, "$pkg->{name}-$ver";
		print <<~EOF;
		(define-public $pkg->{name}-$ver
		  (package
		    (name "$pkg->{name}-$ver")
		    (version "$pkg->{version}")
		    (source
		      (origin
		        (method url-fetch)
		        (uri "$pkg->{url}")
		        (sha256
		          (base32 "$pkg->{sha256base32}"))))
		    (build-system gnu-build-system)
		    (native-inputs
		EOF

		print '      (list';
		for my $input (@{$pkg->{'native-inputs'}}) {
			my $name = $json->{mappings}{$input}{guix} || $input;
			print "\n       $name";
		}
		print "))\n";

		print "    (inputs\n";
		print '      (list';
		for my $input (@{$pkg->{inputs}}) {
			my $name = $json->{mappings}{$input}{guix} || $input;
			print "\n       $name";
		}
		print "))\n";

		print <<~EOF;
		    (arguments
		      (list
		       #:make-flags '(list (string-append "PREFIX=" %output))
		       #:phases
		       #~(modify-phases %standard-phases
		           (delete 'configure))))
		    (synopsis "$pkg->{description}")
		    (description
		      "$pkg->{'long-description'}")
		    (home-page "$pkg->{'base-url'}/")
		    (license licenses:agpl3+)))

		EOF
		if ($pkg->{type} eq 'latest') {
			print <<~EOF;
			(define-public $pkg->{name}
			  (package
			    (inherit $pkg->{name}-latest)
			    (name "$pkg->{name}")))


			EOF
		}
	}
	print '(list';
	for (@pkgs) {
		print "\n $_";
	}
	print ")\n";
}

sub emit_debian() {
	my %vars = (
		tarballs  => ["tarballs = \\\n"],
		checkouts => ["checkouts = \\\n"],
		destdirs  => ["debian-destdirs = \\\n"],
		ctrlfiles => ["control-files = \\\n"],
		destdebs  => ["destdir-debs = \\\n"],
		debs      => ["debs = \\\n"],
	);

	my @targets = ();

	for my $pkg (@{$json->{packages}}) {
		push @{$vars{tarballs}},  "\t$dir/tarballs/$pkg->{fname} \\\n";
		push @{$vars{checkouts}}, "\t$dir/checkouts/$pkg->{name}-$pkg->{version} \\\n";

		push @targets, <<~EOF;
			$dir/tarballs/$pkg->{fname}:
				mkdir -p \$(\@D)
				wget -O \$\@ \\
					'$pkg->{url}'

			$dir/checkouts/$pkg->{name}-$pkg->{version}: $dir/tarballs/$pkg->{fname}
				mkdir -p \$(\@D)
				tar -C $dir/checkouts/ -xf $dir/tarballs/$pkg->{fname}
				touch \$\@

			EOF


		next if $pkg->{architectures} ne 'any';

		push @{$vars{destdirs}},  "\t$dir/debian-destdir/$pkg->{name}-$pkg->{version}/DEBIAN \\\n";
		push @{$vars{ctrlfiles}}, "\t$dir/debian-destdir/$pkg->{name}-$pkg->{version}/DEBIAN/control \\\n";
		push @{$vars{destdebs}},  "\t$dir/debian-destdir/$pkg->{name}-$pkg->{version}.deb \\\n";
		push @{$vars{debs}},      "\t$dir/debian/$pkg->{name}_$pkg->{version}_all.deb \\\n";

		my $ver = $pkg->{type} eq 'latest' ? '0.' . $pkg->{version} . '.latest' : $pkg->{version};
		$ver =~ s/^v//;
		my $maintainer_b64 = MIME::Base64::encode_base64 $pkg->{maintainer}, '';
		my $desc_b64       = MIME::Base64::encode_base64 $pkg->{description}, '';
		my $long_desc_b64  = MIME::Base64::encode_base64 $pkg->{'long-description'}, '';

		push @targets, <<~EOF;
			$dir/debian-destdir/$pkg->{name}-$pkg->{version}/DEBIAN: $dir/checkouts/$pkg->{name}-$pkg->{version}
				\$(MAKE) \\
					-C $dir/checkouts/$pkg->{name}-$pkg->{version} \\
					install \\
					PREFIX=/usr \\
					DESTDIR="\$\$PWD"/$dir/debian-destdir/$pkg->{name}-$pkg->{version}
				mkdir -p \$\@
				touch \$\@

			$dir/debian-destdir/$pkg->{name}-$pkg->{version}/DEBIAN/control: $dir/debian-destdir/$pkg->{name}-$pkg->{version}/DEBIAN
				printf '' > \$\@
				printf 'Package: $pkg->{name}\\n'     >> \$\@
				printf 'Version: $ver\\n'              >> \$\@
				printf 'Section: custom\\n'            >> \$\@
				printf 'Depends:\\n'                   >> \$\@
				printf 'Priority: optional\\n'         >> \$\@
				printf 'Architecture: all\\n'          >> \$\@
				printf 'Essential: no\\n'              >> \$\@

				printf 'Maintainer: '                 >> \$\@
				printf '$maintainer_b64' | base64 -d  >> \$\@
				printf '\\n'                           >> \$\@

				printf 'Description: '                >> \$\@
				printf '$desc_b64' | base64 -d >> \$\@
				printf '\\n'                           >> \$\@

				printf '$long_desc_b64' | \\
					base64 -d |    \\
					sed 's|^\$\$|.|' | \\
					sed 's|^| |'                        >> \$\@
				printf '\\n'                           >> \$\@

			$dir/debian-destdir/$pkg->{name}-$pkg->{version}.deb: $dir/debian-destdir/$pkg->{name}-$pkg->{version}/DEBIAN/control
				dpkg-deb --build $dir/debian-destdir/$pkg->{name}-$pkg->{version}

			$dir/debian/$pkg->{name}_$pkg->{version}_all.deb: $dir/debian-destdir/$pkg->{name}-$pkg->{version}.deb
				mkdir -p \$(\@D)
				cp $dir/debian-destdir/$pkg->{name}-$pkg->{version}.deb \$\@


			EOF
	}


	print
		@{$vars{tarballs}},  "\n",
		@{$vars{checkouts}}, "\n",
		@{$vars{destdirs}},  "\n",
		@{$vars{ctrlfiles}}, "\n",
		@{$vars{destdebs}},  "\n",
		@{$vars{debs}},      "\n";

	print <<~EOF;
		GPGKEY = '$json->{maintainer}'


		all: $dir/debian/InRelease $dir/debian/Release.gpg $dir/debian/public-key.asc

		public-dir:
			\@printf '$dir/debian'


		$dir/debian/Packages: \$(debs)
			cd \$(\@D) && dpkg-scanpackages -m . > \$(\@F)

		$dir/debian/Release: $dir/debian/Packages
			perl src/bin/paku debian-release $dir/debian/Packages > \$\@

		$dir/debian/Release.gpg: $dir/debian/Release
			gpg -abs -o \$\@ $dir/debian/Release

		$dir/debian/InRelease: $dir/debian/Release
			gpg --default-key \$(GPGKEY) -a --clear-sign -o \$\@ $dir/debian/Release

		$dir/debian/public-key.asc: $dir/debian/Release
			gpg --armour --export \$(GPGKEY) > \$\@


		EOF

	print @targets;
}

sub emit_html() {

}


my %actions = (
	'debian-release'  => \&emit_release,
	nix               => \&emit_nix,
	guix              => \&emit_guix,
	debian            => \&emit_debian,
	html              => \&emit_html,
);

my $fn = $actions{$action} or die "Unknown ACTION: \"$action\"";
&$fn;