Browse Source

switch from make to in-house solution

Byron Jones 6 years ago
parent
commit
317c7b7810
9 changed files with 340 additions and 258 deletions
  1. 320 0
      dev-make
  2. 2 2
      dev-precommit
  3. 10 26
      dev-server
  4. 1 120
      dev-util
  5. 5 4
      docs/CONTRIBUTING.md
  6. 1 1
      logbot-web
  7. 0 104
      makefile
  8. 1 1
      web/_variables.sass
  9. 0 0
      web/svg/logbot-favicon.svg

+ 320 - 0
dev-make

@@ -0,0 +1,320 @@
+#!/usr/bin/perl
+use local::lib;
+use v5.10;
+use strict;
+use warnings;
+
+use FindBin qw( $RealBin );
+use lib "$RealBin/lib";
+
+use File::Basename qw( basename );
+use File::Copy qw( copy );
+use File::Find qw( find );
+use List::Util qw( any );
+use LogBot::Util qw( file_time run slurp spurt touch );
+
+$| = 1;
+
+my $FORCE = any { $_ eq '-B' } @ARGV;
+my $DEPS  = any { $_ eq 'deps' } @ARGV;
+my $QUIET = !$DEPS && any { $_ eq '-q' } @ARGV;
+my $DEBUG = $ENV{DEBUG};
+
+#<<<
+my $def = {
+    'web/public/static/logbot.min.js' => [
+        { 'web/build/jquery.min.js'  => { js => 'web/jquery/jquery-3.2.1.min.js' } },
+        { 'web/build/pikaday.min.js' => { js => 'web/pikaday/pikaday.js' } },
+        { 'web/build/chosen.min.js'  => { js => 'web/chosen/chosen.jquery.js' } },
+        { 'web/build/flot.min.js'    => { js => 'web/flot/jquery.flot.js' } },
+        { 'web/build/logbot.min.js'  => { js => 'web/logbot.js' } },
+    ],
+    'web/public/static/logbot.min.css' => [
+        { 'web/build/pikaday.min.css' => { css    => 'web/pikaday/pikaday.scss' } },
+        { 'web/build/hind.min.css'    => { css    => 'web/hind/hind.sass' } },
+        { 'web/build/chosen.min.css'  => { css    => 'web/chosen/chosen.css' } },
+        { 'web/build/logbot.min.css'  => { lb_css => [qw( web/logbot.sass web/_variables.sass )] } },
+    ],
+    _svg => [
+        qw(
+            web/svg/*.svg
+            web/svg/font-awesome/*
+            web/templates/*.html.ep
+            web/templates/layouts/*.html.ep
+            web/templates/shared/*.html.ep
+        )
+    ],
+    _cp => [
+        qw(
+            web/chosen/chosen-sprite.png
+            web/chosen/chosen-sprite@2x.png
+            web/hind/hind-bold.ttf
+            web/hind/hind-medium.ttf
+            web/hind/hind-regular.ttf
+            web/svg/logbot-favicon.svg
+        )
+    ],
+};
+#>>>
+
+mkdir("$RealBin/web/build");
+
+# switching debug mode on/off forces a rebuild
+my $last_mode_file = "$RealBin/web/build/last_mode";
+my $last_mode      = -e $last_mode_file ? slurp($last_mode_file) : '';
+my $this_mode      = $DEBUG ? 'debug' : 'production';
+$FORCE ||= $this_mode ne $last_mode;
+
+my @deps;
+foreach my $target (sort keys %{$def}) {
+
+    if ($target eq '_cp') {
+
+        # copy file
+        foreach my $source_file (@{ $def->{_cp} }) {
+            $source_file = "$RealBin/$source_file";
+            (push(@deps, $source_file), next) if $DEPS;
+            cp($source_file, "$RealBin/web/public/static/" . basename($source_file));
+        }
+
+    } elsif ($target eq '_svg') {
+
+        # if any svg or templates are changed, inline svg
+        my $target_file = "$RealBin/web/build/inline-svg.updated";
+        foreach my $source_file (expand($def->{_svg})) {
+            (push(@deps, $source_file), next) if $DEPS;
+            next unless update($source_file, $target_file);
+            inline_svg();
+            touch($target_file);
+            last;
+        }
+
+    } else {
+
+        # concatenated targets
+        my $target_file = "$RealBin/$target";
+        my @content;
+        my $dirty = 0;
+        foreach my $sub (@{ $def->{$target} }) {
+            my $sub_target      = (keys %{$sub})[0];
+            my $sub_target_file = "$RealBin/$sub_target";
+            my $action          = (keys %{ $sub->{$sub_target} })[0];
+            my $action_source   = $sub->{$sub_target}->{$action};
+
+            if ($action eq 'css') {
+                my $sub_source_file = "$RealBin/$action_source";
+                (push(@deps, $sub_source_file), next) if $DEPS;
+                css($sub_source_file, $sub_target_file) && ($dirty = 1);
+
+            } elsif ($action eq 'lb_css') {
+                foreach my $sub_source_file (expand($action_source)) {
+                    (push(@deps, $sub_source_file), next) if $DEPS;
+                    next unless update($sub_source_file, $sub_target_file);
+                    logbot_css("$RealBin/web/build/logbot.full.css");
+                    css("$RealBin/web/build/logbot.full.css", $sub_target_file);
+                    $dirty = 1;
+                    last;
+                }
+
+            } elsif ($action eq 'js') {
+                my $sub_source_file = "$RealBin/$action_source";
+                (push(@deps, $sub_source_file), next) if $DEPS;
+                js($sub_source_file, $sub_target_file) && ($dirty = 1);
+
+            } else {
+                die $action;
+            }
+
+            push @content, slurp($sub_target_file) unless $DEPS;
+        }
+
+        if ($dirty && !$DEPS) {
+            l(undef, $target_file);
+            spurt($target_file, join('', @content));
+        }
+    }
+}
+
+(say(join("\n", @deps)), exit) if $DEPS;
+spurt($last_mode_file, $this_mode);
+
+sub cp {
+    my ($source_file, $target_file) = @_;
+    return unless update($source_file, $target_file);
+    l($source_file, $target_file);
+
+    copy($source_file, $target_file) || die "$!\n";
+
+    return 1;
+}
+
+sub css {
+    my ($source_file, $target_file) = @_;
+    return unless update($source_file, $target_file);
+    l($source_file, $target_file);
+
+    if ($ENV{DEBUG}) {
+        run('sass', '--style', 'expanded', $source_file, $target_file);
+    } else {
+        run('sass', '--style', 'compressed', $source_file, $target_file);
+    }
+
+    my $css = slurp($target_file);
+    $css =~ s{/\*.*?\*/}{}gs;
+    spurt($target_file, $css);
+
+    return 1;
+}
+
+sub js {
+    my ($source_file, $target_file) = @_;
+    return unless update($source_file, $target_file);
+    l($source_file, $target_file);
+
+    if ($ENV{DEBUG}) {
+        run('uglifyjs', $source_file, '--beautify', '--output', $target_file);
+    } else {
+        run('uglifyjs', $source_file, '--compress', '--mangle', '--output', $target_file);
+    }
+
+    return 1;
+}
+
+sub inline_svg {
+
+    # inlines svg images into templates
+    # to use, create an svg element with a class name "svg-filename"
+    # the filename will be loaded from web/svg and replace the svg element
+    # only the svg-filename class will be carried over into the inline svg element,
+    # other classes and ids will be lost
+    # eg. <svg class="svg-sidebar-collapse"></svg>
+
+    my @files;
+    find(
+        sub {
+            my $file = $File::Find::name;
+            return unless -f $file && -s $file;
+            return unless $file =~ /\.html\.ep$/;
+            push @files, $file;
+        },
+        "$RealBin/web/templates"
+    );
+
+    foreach my $file (sort @files) {
+        my $orig = slurp($file);
+        (my $tmpl = $orig) =~ s{
+            <svg\sclass="svg-([^"]+)".+?</svg>
+        }{
+            load_svg($1)
+        }gxe;
+
+        next if $orig eq $tmpl;
+        spurt($file, $tmpl);
+        $file =~ s/^\Q$RealBin//;
+        l(undef, $file);
+    }
+}
+
+sub logbot_css {
+    my ($target_file) = @_;
+
+    # build css that contains light and dark modes
+
+    # the combined sass looks like:
+    #   variables
+    #   body
+    #       logbot.sass unmodified
+    #   body.dark
+    #       logbot.sass with colour variables prefixed by `night-`
+    #
+    # to avoid duplicate styles in the body.dark section, non-colour styles are
+    # then commented out.  the sass processor will remove these comments from
+    # the final minified css.
+
+    my @variables = split(/\n/, slurp("$RealBin/web/_variables.sass"));
+    my @sass      = split(/\n/, slurp("$RealBin/web/logbot.sass"));
+
+    # remove @import from main sass as we'll be inlining here
+    @sass = grep { !/^\@import\s+variables/ } @sass;
+
+    # indent sass
+    foreach my $line (@sass) {
+
+        # existing body definitions need special treatment
+        if ($line eq 'body') {
+            $line = '&.root';
+        }
+        $line = '  ' . $line;
+    }
+
+    # build duplicate styles, light and dark
+    my @combined;
+    push @combined, @variables;
+    push @combined, '';
+    push @combined, 'body';
+    push @combined, @sass;
+    push @combined, 'body.dark';
+    foreach my $line (@sass) {
+        $line =~ s/\$(.+?-colour)/\$night-$1/g;
+        push @combined, $line;
+    }
+
+    # convert to css (easier to parse)
+    open(my $sass_cmd, '|-', 'sass', '--stdin', '--indented', 'web/build/logbot.combined.css') or die $!;
+    print $sass_cmd join("\n", @combined);
+    close($sass_cmd) or die $!;
+
+    # commend out body.dark selectors that don't involve colour
+    my @css = split(/\n/, slurp("$RealBin/web/build/logbot.combined.css"));
+    my $selector = '';
+    foreach my $line (@css) {
+        next if $line =~ /^\s*}/;
+        if ($line =~ /{$/) {
+            ($selector = $line) =~ s/^\s+//;
+        } else {
+            next unless $selector =~ /^body\.dark\b/;
+            next
+                if $line =~ /#[a-z0-9]{3,6}/
+                || $line =~ /\brgba?\(/
+                || $line =~ /^\s*filter:/;
+            $line = "/* $line */";
+        }
+    }
+
+    spurt($target_file, join("\n", @css));
+}
+
+sub update {
+    my ($source_file, $target_file) = @_;
+    return 1 if $FORCE;
+    return 1 if !-e $target_file;
+    return file_time($source_file) > file_time($target_file);
+}
+
+sub expand {
+    my ($ra_spec) = @_;
+    my @files;
+    foreach my $spec (@{$ra_spec}) {
+        push @files, glob("'$RealBin/$spec'");
+    }
+    return @files;
+}
+
+sub l {
+    my ($source_file, $target_file) = @_;
+    return if $QUIET;
+    $source_file =~ s{^\Q$RealBin/}{} if $source_file;
+    $target_file =~ s{^\Q$RealBin/}{};
+    say $source_file ? "$source_file → $target_file" : "→ $target_file";
+}
+
+sub load_svg {
+    my ($name) = @_;
+    my $file =
+        -e "$RealBin/web/svg/$name.svg" ? "$RealBin/web/svg/$name.svg" : "$RealBin/web/svg/font-awesome/$name.svg";
+    die "failed to find $name.svg\n" unless -e $file;
+    my $svg = slurp($file);
+    $svg =~ s/^<svg /<svg class="svg-$name" /;
+    $svg =~ s/\s+$//;
+    return $svg;
+}

+ 2 - 2
dev-precommit

@@ -199,9 +199,9 @@ foreach my $js_file (qw( web/logbot.js web/redirect.js )) {
     run('jshint', $js_file);
 }
 
-# run make
+# make static assets
 
-run('make', '-B');
+run("$RealBin/dev-make", '-B');
 
 sub sort_use {
     my ($input) = @_;

+ 10 - 26
dev-server

@@ -13,8 +13,6 @@ my @switches = grep {/^-/} @ARGV;
 $ENV{DEBUG} = 1;
 $ENV{LOGBOT_CONFIG} = join(',', grep { !/^-/ } @ARGV) || '_development';
 
-die "failed to find makefile\n" unless -e 'makefile';
-
 # asset watch
 {
     my $pid = fork();
@@ -63,32 +61,25 @@ use warnings;
 use FindBin qw( $RealBin );
 use lib "$RealBin/lib";
 
-use File::Find qw( find );
-use File::Spec ();
 use LogBot::Util qw( file_time run );
 
 sub new {
     my ($class) = @_;
-    return bless({ ts => 0, first => 1 }, $class);
+    return bless({ make => "$RealBin/dev-make", ts => 0, first => 1 }, $class);
 }
 
 sub execute {
     my ($self) = @_;
-    if ($self->{clean}) {
-        run('make', 'clean');
-        delete $self->{clean};
-    }
-    my @command = ('make');
-    push @command, '--quiet' if delete $self->{first};
-    run(@command);
+
+    run($self->{make}, delete $self->{first} ? '-q' : '', delete $self->{all} ? '-B' : '');
 }
 
 sub updated {
     my ($self) = @_;
 
-    if (file_time('makefile') != $self->{ts}) {
-        $self->{clean}    = $self->{ts} != 0;
-        $self->{ts}       = file_time('makefile');
+    if (file_time($self->{make}) != $self->{ts}) {
+        $self->{all}      = $self->{ts} != 0;
+        $self->{ts}       = file_time($self->{make});
         $self->{files}    = $self->_prerequisites();
         $self->{files_ts} = {};
     }
@@ -111,17 +102,10 @@ sub updated {
 
 sub _prerequisites {
     my ($self) = @_;
-    my $web_path = $RealBin . '/web';
-    my @files;
-    find(
-        sub {
-            return unless /\.(?:js|sass|svg)$/;
-            my $rel_file = File::Spec->abs2rel($File::Find::name, $web_path);
-            return if $rel_file =~ m{^(?:public/static|build)/};
-            push @files, $File::Find::name;
-        },
-        $web_path
-    );
+
+    open(my $dm, '-|', $self->{make}, 'deps') or die $!;
+    chomp(my @files = <$dm>);
+    close($dm) or die $!;
     return \@files;
 }
 

+ 1 - 120
dev-util

@@ -17,63 +17,11 @@ $command || die <<'EOF';
 syntax: dev-util <command> [command args]
 
 commands:
-    svg-inline          update templates to inline svg
     svg-import <path>   update svg source from font-awesome
     sass-make           create sass dark mode css
 EOF
 
-if ($command eq 'svg-inline') {
-    my $svg_path       = $RealBin . '/web/svg';
-    my $fa_path        = $svg_path . '/font-awesome';
-    my $templates_path = "$RealBin/web/templates";
-
-    # inlines svg images into templates
-    # to use, create an svg element with a class name "svg-filename"
-    # the filename will be loaded from web/svg and replace the svg element
-    # only the svg-filename class will be carried over into the inline svg element,
-    # other classes and ids will be lost
-    # eg. <svg class="svg-sidebar-collapse"></svg>
-
-    sub load_svg {
-        my ($name) = @_;
-        my $file =
-            -e "$fa_path/$name.svg"
-            ? "$fa_path/$name.svg"
-            : "$svg_path/$name.svg";
-        die "failed to find $name.svg\n" unless -e $file;
-        my $svg = Mojo::File->new($file)->slurp();
-        $svg =~ s/^<svg /<svg class="svg-$name" /;
-        $svg =~ s/\s+$//;
-        return $svg;
-    }
-
-    my @files;
-    find(
-        sub {
-            my $file = $File::Find::name;
-            return unless -f $file && -s $file;
-            return unless $file =~ /\.html\.ep$/;
-
-            push @files, $file;
-        },
-        $templates_path
-    );
-
-    foreach my $file (sort @files) {
-        my $orig = Mojo::File->new($file)->slurp();
-        (my $tmpl = $orig) =~ s{
-            <svg\sclass="svg-([^"]+)".+?</svg>
-        }{
-            load_svg($1)
-        }gxe;
-
-        next if $orig eq $tmpl;
-        say substr($file, length($templates_path) + 1);
-        Mojo::File->new($file)->spurt($tmpl);
-        say "inline-svg: \e[34mupdated\e[0m ", substr($file, length($RealBin) + 1);
-    }
-
-} elsif ($command eq 'svg-import') {
+if ($command eq 'svg-import') {
     my ($source_path)  = @args;
     my $svg_path       = $RealBin . '/web/svg';
     my $fa_path        = $svg_path . '/font-awesome';
@@ -109,71 +57,4 @@ if ($command eq 'svg-inline') {
         }
     }
 
-} elsif ($command eq 'sass-make') {
-    chdir($RealBin);
-
-    # build css that contains light and dark modes
-
-    # the combined sass looks like:
-    #   variables
-    #   body
-    #       logbot.sass unmodified
-    #   body.dark
-    #       logbot.sass with colour variables prefixed by `night-`
-    #
-    # to avoid duplicate styles in the body.dark section, non-colour styles are
-    # then commented out.  the sass processor will remove these comments from
-    # the final minified css.
-
-    my @variables = split(/\n/, Mojo::File->new("$RealBin/web/_variables.sass")->slurp);
-    my @sass      = split(/\n/, Mojo::File->new("$RealBin/web/logbot.sass")->slurp);
-
-    # remove @import from main sass as we'll be inlining here
-    @sass = grep { !/^\@import\s+variables/ } @sass;
-
-    # indent sass
-    foreach my $line (@sass) {
-
-        # existing body definitions need special treatment
-        if ($line eq 'body') {
-            $line = '&.root';
-        }
-        $line = '  ' . $line;
-    }
-
-    # build duplicate styles, light and dark
-    my @combined;
-    push @combined, @variables;
-    push @combined, '';
-    push @combined, 'body';
-    push @combined, @sass;
-    push @combined, 'body.dark';
-    foreach my $line (@sass) {
-        $line =~ s/\$(.+?-colour)/\$night-$1/g;
-        push @combined, $line;
-    }
-
-    # convert to css (easier to parse)
-    open(my $sass_cmd, '|-', 'sass', '--stdin', '--indented', 'web/build/logbot.combined.css') or die $!;
-    print $sass_cmd join("\n", @combined);
-    close($sass_cmd) or die $!;
-
-    # commend out body.dark selectors that don't involve colour
-    my @css = split(/\n/, Mojo::File->new("$RealBin/web/build/logbot.combined.css")->slurp);
-    my $selector = '';
-    foreach my $line (@css) {
-        next if $line =~ /^\s*}/;
-        if ($line =~ /{$/) {
-            ($selector = $line) =~ s/^\s+//;
-        } else {
-            next unless $selector =~ /^body\.dark\b/;
-            next
-                if $line =~ /#[a-z0-9]{3,6}/
-                || $line =~ /\brgba?\(/
-                || $line =~ /^\s*filter:/;
-            $line = "/* $line */";
-        }
-    }
-
-    say join("\n", @css);
 }

+ 5 - 4
docs/CONTRIBUTING.md

@@ -12,9 +12,10 @@ The very high level install steps for a development setup are:
 - run `./logbot-irc development` to start the irc server
 - run `./logbot-consumer development` to start the message->database consumer
 
-`dev-server` will automatically run `make` when web resources are updated.
-If `dev-server` is not running you must run `make` manually.  Some changes
-require a full rebuild of the resources with `make -B`.
+`dev-server` will automatically run `dev-make` when web resources are updated.
+If `dev-server` is not running you must run `dev-make` manually.  Some changes
+require a full rebuild of the resources with `dev-make -B`.
 
 Run `dev-precommit` before creating any pull requests; this will auto-format
-perl, sass, and javascript, and report possible perl or javascript issues.
+perl, sass, and javascript, report possible perl or javascript issues, and
+run `dev-make -B`.

+ 1 - 1
logbot-web

@@ -5,7 +5,7 @@
 # in production runs as a daemon behind a reverse proxy, which handles requests
 # for static assets (web/public/static)
 #
-# use `make` to build static assets
+# use `dev-make` to build static assets
 use local::lib;
 use v5.10;
 use strict;

+ 0 - 104
makefile

@@ -1,104 +0,0 @@
-.POSIX:
-.SUFFIXES:
-
-define sass
-	sass --style compressed "web/$1.sass" "web/build/$2.min.css"
-endef
-
-define scss
-	sass --style compressed "web/$1.scss" "web/build/$2.min.css"
-endef
-
-define css
-	sass --style compressed "web/$1.css" "web/build/$2.min.css"
-endef
-
-define js
-	uglifyjs "web/$1.js" --compress --mangle --output "web/build/$2.min.js"
-endef
-
-EXT_JS=web/build/jquery.min.js web/build/pikaday.min.js web/build/chosen.min.js web/build/flot.min.js
-EXT_CSS=web/build/pikaday.min.css web/build/hind.min.css web/build/chosen.min.css
-REDIR_EXT_JS=web/build/url.min.js
-
-all: \
-	web/public/static/logbot.min.js \
-	web/public/static/logbot.min.css \
-	web/public/static/redirect.min.js \
-	web/public/static/redirect.min.css \
-	web/build/inline-svg.updated \
-	web/public/static/logbot-favicon.svg
-
-clean:
-	rm -f web/build/*.min.{js,css} web/public/static/*.min.{js,css} web/public/static/logbot-favicon.svg
-
-.PHONY: all clean
-
-# redirect
-
-web/public/static/redirect.min.js: web/build/redirect.min.js $(REDIR_EXT_JS)
-	cat $(REDIR_EXT_JS) web/build/redirect.min.js > web/public/static/redirect.min.js
-
-web/build/redirect.min.js: web/redirect.js
-	$(call js,redirect,redirect)
-
-web/build/url.min.js: web/URL/url.js
-	$(call js,URL/url,url)
-
-web/public/static/redirect.min.css: web/build/redirect.min.css
-	cp web/build/redirect.min.css web/public/static/redirect.min.css
-
-web/build/redirect.min.css: web/redirect.sass
-	$(call sass,redirect,redirect)
-
-# javascript
-
-web/public/static/logbot.min.js: web/build/logbot.min.js $(EXT_JS)
-	cat $(EXT_JS) web/build/logbot.min.js > web/public/static/logbot.min.js
-
-web/build/logbot.min.js: web/logbot.js
-	$(call js,logbot,logbot)
-
-web/build/jquery.min.js: web/jquery/jquery-3.2.1.min.js
-	$(call js,jquery/jquery-3.2.1.min,jquery)
-
-web/build/pikaday.min.js: web/pikaday/pikaday.js
-	$(call js,pikaday/pikaday,pikaday)
-
-web/build/chosen.min.js: web/chosen/chosen.jquery.js
-	$(call js,chosen/chosen.jquery,chosen)
-
-web/build/flot.min.js: web/flot/jquery.flot.js
-	$(call js,flot/jquery.flot,flot)
-
-# css
-
-web/public/static/logbot.min.css: web/build/logbot.min.css $(EXT_CSS)
-	perl -pi -e 'BEGIN { $$/ = undef } s#/\*.*?\*/##gs' web/build/*.min.css
-	cat $(EXT_CSS) web/build/logbot.min.css > web/public/static/logbot.min.css
-
-web/build/logbot.min.css: web/logbot.sass web/_variables.sass
-	./dev-util sass-make | sass --stdin --style compressed web/build/logbot.min.css
-
-web/build/pikaday.min.css: web/pikaday/pikaday.scss
-	$(call scss,pikaday/pikaday,pikaday)
-
-web/build/chosen.min.css: web/chosen/chosen.css
-	$(call css,chosen/chosen,chosen)
-	cp web/chosen/*.png web/public/static
-
-web/build/hind.min.css: web/hind/hind.sass
-	$(call sass,hind/hind,hind)
-	cp web/hind/*.ttf web/public/static
-
-# templates
-
-web/build/inline-svg.updated: web/svg/*.svg web/svg/font-awesome/*.svg web/templates/*.html.ep web/templates/layouts/*.html.ep
-	./dev-util svg-inline
-	touch web/build/inline-svg.updated
-
-# svg
-
-web/public/static/logbot-favicon.svg: web/svg/favicon.svg
-	cp web/svg/favicon.svg web/public/static/logbot-favicon.svg
-

+ 1 - 1
web/_variables.sass

@@ -1,4 +1,4 @@
-// see 'sass-make' in dev-util for details on how light and dark mode css are
+// see 'logbot_css' in dev-make for details on how light and dark mode css are
 // built from logbot.sass and this file.
 
 // light colours

+ 0 - 0
web/svg/favicon.svg → web/svg/logbot-favicon.svg