]> Dogcows Code - chaz/git-codeowners/commitdiff
add fatpack script
authorCharles McGarvey <chazmcgarvey@brokenzipper.com>
Sun, 10 Nov 2019 00:33:21 +0000 (17:33 -0700)
committerCharles McGarvey <chazmcgarvey@brokenzipper.com>
Sun, 10 Nov 2019 00:42:48 +0000 (17:42 -0700)
.gitignore
Makefile
bin/git-codeowners
dist.ini
maint/branch_solo.pl [new file with mode: 0755]
maint/fatpack.pl [new file with mode: 0755]

index 2df827a289924b9df6ea660c8814f54583ec9ff5..6b495eefb6916923aefb94581232b9bb50777193 100644 (file)
@@ -4,4 +4,6 @@
 /.perl-version
 /App-Codeowners-*
 /cover_db
+/fatlib
+/git-codeowners
 /local
index 1df821f815588749605932ff4ff8f9ceb8b2a508..74425b9e8f8247c46cc1ced7d2da144c7ff13ea8 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ dist:
        $(DZIL) build
 
 distclean: clean
-       rm -rf cover_db
+       rm -rf .build cover_db fatlib git-codeowners
 
 run:
        $(PERL) -Ilib bin/git-codeowners $(GIT_CODEOWNERS_FLAGS)
index 60e79b27bcd2a8d04b67ca38435c6f06fe718e68..231b16bada4ced86b70ca600058f3663fc4e0b2e 100755 (executable)
 
 =head1 DESCRIPTION
 
-F<git-codeowners> is yet another CLI tool for managing F<CODEOWNERS> files in
-git repos. In particular, it can be used to quickly find out who owns
-a particular file in a monorepo (or monolith).
+F<git-codeowners> is yet another CLI tool for managing F<CODEOWNERS> files in git repos. In
+particular, it can be used to quickly find out who owns a particular file in a monorepo (or
+monolith).
 
-B<THIS IS EXPERIMENTAL!> The interface of this tool and its modules will
-probably change as I field test some things. Feedback welcome.
+B<THIS IS EXPERIMENTAL!> The interface of this tool and its modules will probably change as I field
+test some things. Feedback welcome.
+
+=head1 INSTALL
+
+There are several ways to install F<git-codeowners> to your system.
+
+=head2 from CPAN
+
+You can install F<git-codeowners> using L<cpanm>:
+
+    cpanm App::Codeowners
+
+=head2 from GitHub
+
+You can also choose to download F<git-codeowners> as a self-contained executable:
+
+    curl -OL https://raw.githubusercontent.com/chazmcgarvey/git-codeowners/solo/git-codeowners
+    chmod +x git-codeowners
+
+To hack on the code, clone the repo instead:
+
+    git clone https://github.com/chazmcgarvey/git-codeowners.git
+    cd git-codeowners
+    make bootstrap      # installs dependencies; requires cpanm
 
 =head1 OPTIONS
 
index c7fe9e69be063e7e01400182f6efacdf8eb54bfc..ca06b979b4ffb44508c93e3b950b877ff390b2ec 100644 (file)
--- a/dist.ini
+++ b/dist.ini
@@ -11,9 +11,13 @@ license             = Perl_5
 -remove             = PodCoverageTests
 -remove             = Test::CleanNamespaces
 max_target_perl     = 5.10.1
+PruneFiles.filename = maint
 
 [ConsistentVersionTest]
 
+[Run::Release]
+run                 = %x maint%pbranch_solo.pl %v %d
+
 [RemovePhasedPrereqs]
 remove_runtime      = JSON::MaybeXS
 remove_runtime      = Text::CSV
@@ -28,4 +32,16 @@ JSON::MaybeXS       = 0
 Text::CSV           = 0
 Text::Table         = 0
 YAML                = 0
+[Prereqs / DevelopRecommends]
+; for fatpack.pl
+App::FatPacker      = 0
+CPAN::Meta          = 0
+Capture::Tiny       = 0
+Config              = 0
+File::pushd         = 0
+Getopt::Long        = 0
+MetaCPAN::API       = 0
+Module::CoreList    = 0
+Path::Tiny          = 0
+Perl::Strip         = 0
 
diff --git a/maint/branch_solo.pl b/maint/branch_solo.pl
new file mode 100755 (executable)
index 0000000..aa44384
--- /dev/null
@@ -0,0 +1,61 @@
+#!/usr/bin/env perl
+
+# This script prepares the git-codeowners script for standalone use and puts it in a new branch.
+
+use strict;
+use warnings;
+use autodie;
+
+use File::Copy;
+use File::Path qw(make_path remove_tree);
+use String::ShellQuote;
+
+my $version = shift or die 'Need version';
+my $distdir = shift or die 'Need distdir';
+
+my $branch_name = 'solo';
+my $script_name = 'git-codeowners';
+
+my $branch_oldref = '';
+my $branch_oldref_origin = '';
+
+open(my $fh, '-|', qw{git show-ref}, $branch_name);
+while (my $line = <$fh>) {
+    chomp $line;
+    my ($hash, $ref) = split(/\s+/, $line);
+    $branch_oldref = $hash          if $ref eq "refs/heads/$branch_name";
+    $branch_oldref_origin = $hash   if $ref eq "refs/remotes/origin/$branch_name";
+}
+if ($branch_oldref_origin && $branch_oldref ne $branch_oldref_origin) {
+    # reset local branch
+    system(qw{git branch -f}, $branch_name, "origin/$branch_name");
+    $branch_oldref = $branch_oldref_origin
+}
+
+my $commit_msg = shell_quote("Release $version");
+
+my $solodir = "solo_branch.$$";
+make_path($solodir);
+
+use Config;
+system($Config{'perlpath'}, qw{maint/fatpack.pl --clean --dist}, $distdir);
+move($script_name, "$solodir/$script_name");
+
+copy("$distdir/README", "$solodir/README");
+
+system(qw{git update-index --add}, glob("$solodir/*"));
+my $tree_ref = `git write-tree --prefix=$solodir/`;
+chomp $tree_ref;
+
+system(qw{git reset});
+remove_tree($solodir);
+
+my $branch_oldref_safe = shell_quote($branch_oldref);
+my $tree_ref_safe = shell_quote($tree_ref);
+my $parent = $branch_oldref ? "-p $branch_oldref_safe" : '';
+my $commit_ref = `git commit-tree -m $commit_msg $parent $tree_ref_safe`;
+chomp $commit_ref;
+
+system(qw(git branch -f), $branch_name, $commit_ref);
+system(qw(git tag -a -m), "Version $version", "$branch_name-$version", $commit_ref);
+
diff --git a/maint/fatpack.pl b/maint/fatpack.pl
new file mode 100755 (executable)
index 0000000..921380c
--- /dev/null
@@ -0,0 +1,240 @@
+#!/usr/bin/env perl
+
+=head1 NAME
+
+    maint/fatpack.pl - Generate a fatpack version of git-codeowners
+
+=head1 SYNOPSIS
+
+    maint/fatpack.pl --dist-dir DIRPATH [--clean]
+
+=cut
+
+use 5.010001;
+use strict;
+use warnings;
+
+use CPAN::Meta;
+use Capture::Tiny qw(capture_stdout);
+use Config;
+use File::pushd;
+use Getopt::Long;
+use MetaCPAN::API;
+use Module::CoreList;
+use Path::Tiny;
+
+my $core_version        = '5.010001';
+my $plenv_version       = '5.10.1';
+my %blacklist_modules   = map { $_ => 1 } (
+    'perl',
+    'Text::Table::ASV',     # brought in by Text::Table::Any but not actually required
+    'Unicode::GCString',    # optional XS module
+);
+my @extra_modules = (
+    'Proc::Find::Parents',  # used by Term::Detect::Software on some platforms
+);
+
+my $clean = 0;
+my $distdir;
+GetOptions(
+    'clean!'    => \$clean,
+    'dist=s'    => \$distdir,
+) or die "Invalid options.\n";
+$distdir && -d $distdir or die "Use --dist to specify path to a distribution directory.\n";
+
+my $mcpan = MetaCPAN::API->new;
+
+run($distdir, $clean);
+exit;
+
+sub install_modules {
+    my $path    = path(shift);
+    my @modules = @_;
+    run_command('cpanm', '-n', "-L$path", @modules);
+}
+
+sub run {
+    my $distdir = path(shift);
+    my $clean   = shift;
+
+    my $builddir = path('.build');
+    my $fatlibdir = path('fatlib');
+
+    if ($clean) {
+        print STDERR "Cleaning...\n";
+        $builddir->remove_tree({safe => 0});
+        $fatlibdir->remove_tree({safe => 0});
+    }
+
+    $builddir->mkpath;
+
+    my @modules = required_modules($distdir, $builddir->child('deps.txt'));
+    install_modules($builddir->child('local'), @modules);
+    pack_modules($builddir->child('local'), @modules);
+
+    clean_fatlib($fatlibdir);
+
+    # consolidate all modules into a new directory for packing
+    my $moduledir = $builddir->child('modules');
+    $moduledir->remove_tree({safe => 0});
+    $moduledir->mkpath;
+    system(qw{cp -r}, $fatlibdir, $distdir->child('lib'), "$moduledir/");
+
+    $moduledir->child('lib/Test/File/Codeowners.pm')->remove;    # don't need this
+
+    my $fatpack = do {
+        my $cd_builddir = pushd($moduledir);
+
+        system('perlstrip', '--cache', '-v', find_modules('.'));
+        `fatpack file`;
+    };
+
+    generate_script($distdir->child('bin/git-codeowners'), $fatpack, 'git-codeowners');
+}
+
+sub required_modules {
+    my $path            = path(shift);
+    my $cache_filepath  = shift;
+
+    print STDERR "Determining required modules...\n";
+
+    my $cachefile = $cache_filepath && path($cache_filepath);
+    if (my $contents = eval { $cachefile->slurp_utf8 }) {
+        chomp $contents;
+        return split(/\n/, $contents);
+    }
+
+    my $meta = CPAN::Meta->load_file($path->child('META.json'));
+
+    my $requires = CPAN::Meta::Requirements->new;
+
+    for my $type (qw{requires recommends suggests}) {
+        my $reqs = $meta->effective_prereqs->requirements_for('runtime', $type);
+        for my $module ($reqs->required_modules) {
+            next if $blacklist_modules{$module};
+
+            my $core = $Module::CoreList::version{$core_version}{$module};
+            print STDERR "skipping core: $module $core\n" if $core;
+            next if $core && $reqs->accepts_module($module, $core);
+
+            $requires->add_string_requirement($module => $reqs->requirements_for_module($module));
+            dependencies_for_module($requires, $module);
+        }
+    }
+    $requires->clear_requirement($_) for qw(Module::CoreList ExtUtils::MakeMaker Carp);
+    my @deps = $requires->required_modules;
+
+    push @deps, @extra_modules;
+
+    $cachefile->spew_utf8([map { "$_\n" } @deps]) if $cachefile;
+
+    return @deps;
+}
+
+sub dependencies_for_dist {
+    my $requires    = shift;
+    my $name        = shift;
+
+    state %dists;
+    return if $dists{$name}++;
+    print STDERR "Finding dependencies for dist $name\n";
+
+    my $dist = $mcpan->release(distribution => $name);
+
+    my $reqs = CPAN::Meta::Requirements->new;
+
+    foreach my $dep (@{$dist->{dependency}}) {
+        next if $dep->{phase} ne 'runtime';
+        next if $dep->{relationship} ne 'requires'; # && $dep->{relationship} ne 'recommends';
+
+        my $module = $dep->{module};
+        next if $blacklist_modules{$module};
+
+        $reqs->add_minimum($dep->{module} => $dep->{version});
+        my $core = $Module::CoreList::version{$core_version}{$module};
+        print STDERR "skipping core: $module $core\n" if $core;
+        next if $core && $reqs->accepts_module($module, $core);
+
+        $requires->add_string_requirement($module => $reqs->requirements_for_module($module));
+        dependencies_for_module($requires, $dep->{module});
+    }
+}
+
+sub dependencies_for_module {
+    my $requires    = shift;
+    my $name        = shift;
+
+    state %modules;
+    return if $modules{$name}++;
+    print STDERR "Finding dependencies for module $name\n";
+
+    my $module = $mcpan->module($name);
+    dependencies_for_dist($requires, $module->{distribution});
+}
+
+sub clean_fatlib {
+    my $path = path(shift);
+    $path->child($Config{archname})->remove_tree({safe => 0});
+    $path->child('POD2')->remove_tree({safe => 0});
+    $path->visit(sub {
+        local $_ = shift;
+        if (/\.p(od|l)$/ || /\.sample$/) {
+            print "rm $_\n";
+            $_->remove;
+        }
+    }, {recurse => 1});
+}
+
+sub find_modules {
+    my $path = path(shift);
+    my @pm_filepaths;
+    $path->visit(sub {
+        local $_ = shift;
+        push @pm_filepaths, $_ if /\.pm$/;
+    }, {recurse => 1});
+    return @pm_filepaths;
+}
+
+sub pack_modules {
+    my ($path, @modules) = @_;
+
+    my @filepaths = map { my $s = $_; $s =~ s!::!/!g; "$s.pm" } @modules;
+
+    my $stdout = capture_stdout {
+        local $ENV{PERL5LIB} = $path->child('lib/perl5')->absolute;
+        system('fatpack', 'packlists-for', @filepaths);
+    };
+
+    my @packlists = split(/\n/, $stdout);
+    for my $packlist (@packlists) {
+        warn "Packing $packlist\n";
+    }
+
+    system('fatpack', 'tree', map { path($_)->absolute } @packlists);
+}
+
+sub generate_script {
+    my ($input_filepath, $fatpack, $output_filepath) = @_;
+
+    open(my $in,  '<', $input_filepath)        or die "open failed: $!";
+    open(my $out, '>', "$output_filepath.tmp") or die "open failed: $!";
+
+    while (<$in>) {
+        s|^#!\h*perl|#!/usr/bin/env perl|;
+        s|^# FATPACK.*|$fatpack|;
+        print $out $_;
+    }
+
+    unlink($output_filepath);
+    rename("$output_filepath.tmp", $output_filepath);
+
+    path($output_filepath)->chmod(0755);
+
+    print STDERR "Wrote fatpacked script: $output_filepath\n";
+}
+
+sub run_command {
+    local $ENV{PLENV_VERSION} = $plenv_version;
+    system('plenv', 'exec', @_);
+}
+
This page took 0.035696 seconds and 4 git commands to generate.