diff --git a/.gitignore b/.gitignore
index 6ce0311e4ff1..903e226ca9b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,8 +34,11 @@ _testmain.go
 coverage.all
 
 /modules/options/bindata.go
+/modules/options/bindata.go.hash
 /modules/public/bindata.go
+/modules/public/bindata.go.hash
 /modules/templates/bindata.go
+/modules/templates/bindata.go.hash
 
 *.db
 *.log
diff --git a/Makefile b/Makefile
index f5d0fb7f777d..ecdc5dac3a07 100644
--- a/Makefile
+++ b/Makefile
@@ -52,6 +52,7 @@ CSS_SOURCES ?= $(shell find web_src/less -type f)
 JS_DEST := public/js/index.js
 CSS_DEST := public/css/index.css
 BINDATA_DEST := modules/public/bindata.go modules/options/bindata.go modules/templates/bindata.go
+BINDATA_HASH := $(addsuffix .hash,$(BINDATA_DEST))
 
 JS_DEST_DIR := public/js
 CSS_DEST_DIR := public/css
@@ -145,7 +146,7 @@ clean-all: clean
 .PHONY: clean
 clean:
 	$(GO) clean -i ./...
-	rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) \
+	rm -rf $(EXECUTABLE) $(DIST) $(BINDATA_DEST) $(BINDATA_HASH) \
 		integrations*.test \
 		integrations/gitea-integration-pgsql/ integrations/gitea-integration-mysql/ integrations/gitea-integration-mysql8/ integrations/gitea-integration-sqlite/ \
 		integrations/gitea-integration-mssql/ integrations/indexers-mysql/ integrations/indexers-mysql8/ integrations/indexers-pgsql integrations/indexers-sqlite \
@@ -161,7 +162,7 @@ vet:
 
 .PHONY: generate
 generate: fomantic js css
-	GO111MODULE=on $(GO) generate -mod=vendor $(PACKAGES)
+	GO111MODULE=on $(GO) generate -mod=vendor -tags '$(TAGS)' $(PACKAGES)
 
 .PHONY: generate-swagger
 generate-swagger:
diff --git a/modules/options/main.go b/modules/options/main.go
deleted file mode 100644
index 0bc6c04e24b9..000000000000
--- a/modules/options/main.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// +build ignore
-
-package main
-
-import (
-	"log"
-	"net/http"
-
-	"github.com/shurcooL/vfsgen"
-)
-
-func main() {
-	var fsTemplates http.FileSystem = http.Dir("../../options")
-	err := vfsgen.Generate(fsTemplates, vfsgen.Options{
-		PackageName:  "options",
-		BuildTags:    "bindata",
-		VariableName: "Assets",
-		Filename:     "bindata.go",
-	})
-	if err != nil {
-		log.Fatal("%v", err)
-	}
-}
diff --git a/modules/options/options.go b/modules/options/options.go
index 62e8c041bd79..6ba3bd6a868a 100644
--- a/modules/options/options.go
+++ b/modules/options/options.go
@@ -4,8 +4,6 @@
 
 package options
 
-//go:generate go run -mod=vendor main.go
-
 type directorySet map[string][]string
 
 func (s directorySet) Add(key string, value []string) {
diff --git a/modules/options/options_bindata.go b/modules/options/options_bindata.go
new file mode 100644
index 000000000000..a5143c1fffb0
--- /dev/null
+++ b/modules/options/options_bindata.go
@@ -0,0 +1,9 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//+build bindata
+
+package options
+
+//go:generate go run -mod=vendor ../../scripts/generate-bindata.go ../../options options bindata.go
diff --git a/modules/public/main.go b/modules/public/main.go
deleted file mode 100644
index 707dbe2b220a..000000000000
--- a/modules/public/main.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// +build ignore
-
-package main
-
-import (
-	"log"
-	"net/http"
-
-	"github.com/shurcooL/vfsgen"
-)
-
-func main() {
-	var fsPublic http.FileSystem = http.Dir("../../public")
-	err := vfsgen.Generate(fsPublic, vfsgen.Options{
-		PackageName:  "public",
-		BuildTags:    "bindata",
-		VariableName: "Assets",
-		Filename:     "bindata.go",
-	})
-	if err != nil {
-		log.Fatal("%v", err)
-	}
-}
diff --git a/modules/public/public.go b/modules/public/public.go
index c16c8e0009cf..2617d31aea58 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -18,8 +18,6 @@ import (
 	"gitea.com/macaron/macaron"
 )
 
-//go:generate go run -mod=vendor main.go
-
 // Options represents the available options to configure the macaron handler.
 type Options struct {
 	Directory   string
diff --git a/modules/public/public_bindata.go b/modules/public/public_bindata.go
new file mode 100644
index 000000000000..68a786c76713
--- /dev/null
+++ b/modules/public/public_bindata.go
@@ -0,0 +1,9 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+//+build bindata
+
+package public
+
+//go:generate go run -mod=vendor  ../../scripts/generate-bindata.go ../../public public bindata.go
diff --git a/modules/templates/main.go b/modules/templates/main.go
deleted file mode 100644
index 4460f58cbf14..000000000000
--- a/modules/templates/main.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// +build ignore
-
-package main
-
-import (
-	"log"
-	"net/http"
-
-	"github.com/shurcooL/vfsgen"
-)
-
-func main() {
-	var fsTemplates http.FileSystem = http.Dir("../../templates")
-	err := vfsgen.Generate(fsTemplates, vfsgen.Options{
-		PackageName:  "templates",
-		BuildTags:    "bindata",
-		VariableName: "Assets",
-		Filename:     "bindata.go",
-	})
-	if err != nil {
-		log.Fatal("%v", err)
-	}
-}
diff --git a/modules/templates/templates.go b/modules/templates/templates_bindata.go
similarity index 59%
rename from modules/templates/templates.go
rename to modules/templates/templates_bindata.go
index af6bf010c1d6..eaf64d9457b5 100644
--- a/modules/templates/templates.go
+++ b/modules/templates/templates_bindata.go
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a MIT-style
 // license that can be found in the LICENSE file.
 
+//+build bindata
+
 package templates
 
-//go:generate go run -mod=vendor main.go
+//go:generate go run -mod=vendor ../../scripts/generate-bindata.go ../../templates templates bindata.go
diff --git a/scripts/generate-bindata.go b/scripts/generate-bindata.go
new file mode 100644
index 000000000000..fa1669fcf9fb
--- /dev/null
+++ b/scripts/generate-bindata.go
@@ -0,0 +1,86 @@
+// Copyright 2020 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package main
+
+import (
+	"bytes"
+	"crypto/sha1"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"net/http"
+	"os"
+	"path/filepath"
+	"strconv"
+
+	"github.com/shurcooL/vfsgen"
+)
+
+func needsUpdate(dir string, filename string) (bool, []byte) {
+	needRegen := false
+	_, err := os.Stat(filename)
+	if err != nil {
+		needRegen = true
+	}
+
+	oldHash, err := ioutil.ReadFile(filename + ".hash")
+	if err != nil {
+		oldHash = []byte{}
+	}
+
+	hasher := sha1.New()
+
+	err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+		_, _ = hasher.Write([]byte(info.Name()))
+		_, _ = hasher.Write([]byte(info.ModTime().String()))
+		_, _ = hasher.Write([]byte(strconv.FormatInt(info.Size(), 16)))
+		return nil
+	})
+	if err != nil {
+		return true, oldHash
+	}
+
+	newHash := hasher.Sum([]byte{})
+
+	if bytes.Compare(oldHash, newHash) != 0 {
+
+		return true, newHash
+	}
+
+	return needRegen, newHash
+}
+
+func main() {
+	if len(os.Args) != 4 {
+		log.Fatal("Insufficient number of arguments. Need: directory packageName filename")
+	}
+
+	dir, packageName, filename := os.Args[1], os.Args[2], os.Args[3]
+
+	update, newHash := needsUpdate(dir, filename)
+
+	if !update {
+		fmt.Printf("bindata for %s already up-to-date\n", packageName)
+		return
+	}
+
+	fmt.Printf("generating bindata for %s\n", packageName)
+	var fsTemplates http.FileSystem = http.Dir(dir)
+	err := vfsgen.Generate(fsTemplates, vfsgen.Options{
+		PackageName:  packageName,
+		BuildTags:    "bindata",
+		VariableName: "Assets",
+		Filename:     filename,
+	})
+	if err != nil {
+		log.Fatalf("%v\n", err)
+	}
+	_ = ioutil.WriteFile(filename+".hash", newHash, 0666)
+}