Add build tool-chain

Signed-off-by: Gao Hongtao <hanahmily@gmail.com>
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e75b509
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,88 @@
+mk_path  := $(abspath $(lastword $(MAKEFILE_LIST)))
+mk_dir   := $(dir $(mk_path))
+tool_bin := $(mk_dir)bin
+
+PROJECTS := banyand
+
+##@ Build targets
+
+clean: TARGET=clean test-clean
+clean: default  ## Clean artifacts in all projects
+
+build: TARGET=all
+build: PROJECTS:=$(PROJECTS)
+build: default  ## Build all projects
+
+##@ Release targets
+
+release: TARGET=release
+release: default  ## Build the release artifacts for all projects, usually the statically linked binaries
+
+##@ Test targets
+
+test: TARGET=test
+test: PROJECTS:=$(PROJECTS)
+test: default          ## Run the unit tests in all projects
+
+test-race: TARGET=test-race
+test-race: PROJECTS:=$(PROJECTS)
+test-race: default     ## Run the unit tests in all projects with race detector on
+
+test-coverage: TARGET=test-coverage
+test-coverage: PROJECTS:=$(PROJECTS)
+test-coverage: default ## Run the unit tests in all projects with coverage analysis on
+
+##@ Code quality targets
+
+lint: TARGET=lint
+lint: PROJECTS:=$(PROJECTS)
+lint: default ## Run the linters on all projects
+
+
+##@ Code style targets
+
+# The goimports tool does not arrange imports in 3 blocks if there are already more than three blocks.
+# To avoid that, before running it, we collapse all imports in one block, then run the formatter.
+format: ## Format all Go code
+	@for f in `find . -name '*.go'`; do \
+	    awk '/^import \($$/,/^\)$$/{if($$0=="")next}{print}' $$f > /tmp/fmt; \
+	    mv /tmp/fmt $$f; \
+	done
+	@goimports -w -local github.com/apache/skywalking-banyandb .
+
+# Enforce go version matches what's in go.mod when running `make check` assuming the following:
+# * 'go version' returns output like "go version go1.16 darwin/amd64"
+# * go.mod contains a line like "go 1.16"
+CONFIGURED_GO_VERSION := $(shell sed -ne '/^go /s/.* //gp' go.mod)
+EXPECTED_GO_VERSION_PREFIX := "go version go$(CONFIGURED_GO_VERSION)"
+GO_VERSION := $(shell go version)
+
+## Check that the status is consistent with CI.
+check: clean
+# case statement because /bin/sh cannot do prefix comparison, awk is awkward and assuming /bin/bash is brittle
+	@case "$(GO_VERSION)" in $(EXPECTED_GO_VERSION_PREFIX)* ) ;; * ) \
+		echo "Expected 'go version' to start with $(EXPECTED_GO_VERSION_PREFIX), but it didn't: $(GO_VERSION)"; \
+		echo "Upgrade go to $(CONFIGURED_GO_VERSION)+"; \
+		exit 1; \
+	esac
+	$(MAKE) format
+	mkdir -p /tmp/artifacts
+	git diff >/tmp/artifacts/check.diff 2>&1
+	go mod tidy
+	@if [ ! -z "`git status -s`" ]; then \
+		echo "Following files are not consistent with CI:"; \
+		git status -s; \
+		exit 1; \
+	fi
+
+
+default:
+	@for PRJ in $(PROJECTS); do \
+		echo "--- $$PRJ: $(TARGET) ---"; \
+		$(MAKE) $(TARGET) -C $$PRJ; \
+		if [ $$? -ne 0 ]; then \
+			exit 1; \
+		fi; \
+	done
+
+include scripts/build/help.mk
diff --git a/banyand/.gitignore b/banyand/.gitignore
new file mode 100644
index 0000000..378eac2
--- /dev/null
+++ b/banyand/.gitignore
@@ -0,0 +1 @@
+build
diff --git a/banyand/Makefile b/banyand/Makefile
new file mode 100644
index 0000000..10bf18e
--- /dev/null
+++ b/banyand/Makefile
@@ -0,0 +1,9 @@
+NAME := banyand
+SERVER := $(NAME)-server
+BINARIES := $(SERVER)
+DEBUG_BINARIES := $(SERVER)-debug
+
+include ../scripts/build/build.mk
+include ../scripts/build/test.mk
+include ../scripts/build/lint.mk
+include ../scripts/build/help.mk
diff --git a/banyand/cmd/main.go b/banyand/cmd/server/main.go
similarity index 99%
rename from banyand/cmd/main.go
rename to banyand/cmd/server/main.go
index b6167e8..bbf33f5 100644
--- a/banyand/cmd/main.go
+++ b/banyand/cmd/server/main.go
@@ -32,4 +32,3 @@
 		os.Exit(1)
 	}
 }
-
diff --git a/banyand/internal/bus/bus.go b/banyand/internal/bus/bus.go
index d181dbd..8390884 100644
--- a/banyand/internal/bus/bus.go
+++ b/banyand/internal/bus/bus.go
@@ -115,7 +115,7 @@
 		b.topics[topic] = make([]Channel, 0)
 	}
 	ch := make(Channel)
-	list, _ := b.topics[topic]
+	list := b.topics[topic]
 	list = append(list, ch)
 	b.topics[topic] = list
 	go func(listener MessageListener, ch Channel) {
diff --git a/banyand/internal/cmd/root.go b/banyand/internal/cmd/root.go
index a34e10b..bcd03e4 100644
--- a/banyand/internal/cmd/root.go
+++ b/banyand/internal/cmd/root.go
@@ -19,7 +19,11 @@
 
 package cmd
 
-import "github.com/spf13/cobra"
+import (
+	"github.com/spf13/cobra"
+
+	"github.com/apache/skywalking-banyandb/pkg/version"
+)
 
 const logo = `
 ██████╗  █████╗ ███╗   ██╗██╗   ██╗ █████╗ ███╗   ██╗██████╗ ██████╗ 
@@ -33,6 +37,7 @@
 func NewRoot() *cobra.Command {
 	cmd := &cobra.Command{
 		DisableAutoGenTag: true,
+		Version:           version.Build(),
 		Short:             "BanyanDB is an observability database",
 		Long: logo + `
 BanyanDB, as an observability database, aims to ingest, analyze and store Metrics, Tracing and Logging data
diff --git a/banyand/internal/cmd/standalone.go b/banyand/internal/cmd/standalone.go
index 386ca73..24a551d 100644
--- a/banyand/internal/cmd/standalone.go
+++ b/banyand/internal/cmd/standalone.go
@@ -35,12 +35,14 @@
 	"github.com/apache/skywalking-banyandb/banyand/shard"
 	"github.com/apache/skywalking-banyandb/banyand/storage"
 	"github.com/apache/skywalking-banyandb/pkg/logger"
+	"github.com/apache/skywalking-banyandb/pkg/version"
 )
 
 func newStandaloneCmd() *cobra.Command {
 	standaloneCmd := &cobra.Command{
-		Use:   "standalone",
-		Short: "Run as the standalone mode",
+		Use:     "standalone",
+		Version: version.Build(),
+		Short:   "Run as the standalone mode",
 		RunE: func(cmd *cobra.Command, args []string) (err error) {
 			logger.Log.Info("starting as a standalone server")
 			dataBus := bus.NewBus()
diff --git a/go.mod b/go.mod
index dc54b5a..a5d0d3d 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,6 @@
 require (
 	github.com/spf13/cobra v1.1.3
 	go.uber.org/atomic v1.7.0
-	go.uber.org/multierr v1.6.0 // indirect
+	go.uber.org/multierr v1.6.0
 	go.uber.org/zap v1.16.0
 )
diff --git a/golangci.yml b/golangci.yml
index 2f43dd7..7b4030e 100644
--- a/golangci.yml
+++ b/golangci.yml
@@ -21,7 +21,6 @@
     - golint
     - ineffassign
     - lll
-    - maligned
     - misspell
     - structcheck
     - unconvert
diff --git a/banyand/cmd/main.go b/pkg/version/version.go
similarity index 72%
copy from banyand/cmd/main.go
copy to pkg/version/version.go
index b6167e8..9a6c756 100644
--- a/banyand/cmd/main.go
+++ b/pkg/version/version.go
@@ -17,19 +17,14 @@
  *  under the License.
  */
 
-package main
+// Package version can be used to implement embedding versioning details from
+// git branches and tags into the binary importing this package.
+package version
 
-import (
-	"fmt"
-	"os"
+// build is to be populated at build time using -ldflags -X.
+var build string
 
-	"github.com/apache/skywalking-banyandb/banyand/internal/cmd"
-)
-
-func main() {
-	if err := cmd.NewRoot().Execute(); err != nil {
-		_, _ = fmt.Fprintln(os.Stderr, err)
-		os.Exit(1)
-	}
+// Show the service's build information
+func Build() string {
+	return build
 }
-
diff --git a/scripts/build/build.mk b/scripts/build/build.mk
new file mode 100644
index 0000000..dbeb540
--- /dev/null
+++ b/scripts/build/build.mk
@@ -0,0 +1,70 @@
+
+ifndef NAME
+$(error The NAME variable should be set)
+endif
+
+ifndef BINARIES
+$(error The BINARIES variable should be set to the name binaries to produce)
+endif
+
+STATIC_BINARIES ?= $(addsuffix -static,$(BINARIES))
+DEBUG_BINARIES ?= $(addsuffix -debug,$(BINARIES))
+DEBUG_STATIC_BINARIES ?= $(addsuffix -static,$(DEBUG_BINARIES))
+BUILD_DIR ?= build/bin
+# Define SUB_DIR var if the project is not at root level project
+SOURCE_DIR := $(if $(SUB_DIR),$(SUB_DIR)/$(NAME),$(NAME))
+
+# Retrieve git versioning details so we can add to our binary assets
+VERSION_PATH    := github.com/apache/skywalking-banyandb/pkg/version
+VERSION_STRING  := $(shell git describe --tags --long $(shell git rev-list --tags --max-count=1))
+GIT_BRANCH_NAME := $(shell git rev-parse --abbrev-ref HEAD)
+GO_LINK_VERSION := -X ${VERSION_PATH}.build=${VERSION_STRING}-${GIT_BRANCH_NAME}
+
+##@ Build targets
+
+.PHONY: all
+all: $(BINARIES)  ## Build all the binaries
+
+$(BINARIES): $(NAME)-%: $(BUILD_DIR)/$(NAME)-%
+$(addprefix $(BUILD_DIR)/,$(BINARIES)): $(BUILD_DIR)/$(NAME)-%:
+	@echo "Building binary"
+	go build -v --ldflags '${GO_LINK_VERSION}' -tags "$(BUILD_TAGS)" -o $@ github.com/apache/skywalking-banyandb/$(SOURCE_DIR)/cmd/$*
+	chmod +x $@
+	@echo "Done building $(NAME) $*"
+
+.PHONY: debug
+debug: $(DEBUG_BINARIES)  ## Build the debug binaries
+$(DEBUG_BINARIES): $(NAME)-%-debug: $(BUILD_DIR)/$(NAME)-%-debug
+$(addprefix $(BUILD_DIR)/,$(DEBUG_BINARIES)): $(BUILD_DIR)/$(NAME)-%-debug:
+	@echo "Building debug binary"
+	mkdir -p $(BUILD_DIR)
+	go build -v --ldflags '${GO_LINK_VERSION}' -tags "$(BUILD_TAGS)" -gcflags='all=-N -l' -o $@ github.com/apache/skywalking-banyandb/$(SOURCE_DIR)/cmd/$*
+	chmod +x $@
+	@echo "Done building debug $(NAME) $*"
+
+$(STATIC_BINARIES): $(NAME)-%-static: $(BUILD_DIR)/$(NAME)-%-static
+$(addprefix $(BUILD_DIR)/,$(STATIC_BINARIES)): $(BUILD_DIR)/$(NAME)-%-static:
+	@echo "Building static binary"
+	CGO_ENABLED=0 GOOS=linux go build \
+		-a --ldflags '${GO_LINK_VERSION} -extldflags "-static"' -tags "netgo $(BUILD_TAGS)" -installsuffix netgo \
+		-o $(BUILD_DIR)/$(NAME)-$*-static github.com/apache/skywalking-banyandb/$(SOURCE_DIR)/cmd/$*
+	chmod +x $(BUILD_DIR)/$(NAME)-$*-static
+	@echo "Done building static $(NAME) $*"
+
+.PHONY: debug-static
+debug-static: $(DEBUG_STATIC_BINARIES)  ## Build the debug static binaries
+$(DEBUG_STATIC_BINARIES): $(NAME)-%-debug-static: $(BUILD_DIR)/$(NAME)-%-debug-static
+$(addprefix $(BUILD_DIR)/,$(DEBUG_STATIC_BINARIES)): $(BUILD_DIR)/$(NAME)-%-debug-static:
+	@echo "Building debug static binary"
+	CGO_ENABLED=0 GOOS=linux go build \
+		-a --ldflags '${GO_LINK_VERSION} -extldflags "-static"' -tags "netgo $(BUILD_TAGS)" -gcflags='all=-N -l' -installsuffix netgo \
+		-o $(BUILD_DIR)/$(NAME)-$*-debug-static github.com/apache/skywalking-banyandb/$(SOURCE_DIR)/cmd/$*
+	chmod +x $(BUILD_DIR)/$(NAME)-$*-debug-static
+	@echo "Done building debug static $(NAME) $*"
+
+.PHONY: release
+release: $(STATIC_BINARIES)   ## Build the release binaries
+
+.PHONY: clean
+clean::  ## Clean all artifacts
+	rm -rf $(BUILD_DIR)
diff --git a/scripts/build/help.mk b/scripts/build/help.mk
new file mode 100644
index 0000000..daa2de3
--- /dev/null
+++ b/scripts/build/help.mk
@@ -0,0 +1,7 @@
+##@ Other targets
+
+.PHONY: help
+help:  ## Display this help
+	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n"} \
+			/^[.a-zA-Z0-9_-]+:.*?##/ { printf "  \033[36m%-15s\033[0m %s\n", $$1, $$2 } \
+			/^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST)
diff --git a/scripts/build/lint.mk b/scripts/build/lint.mk
new file mode 100644
index 0000000..2b16d3a
--- /dev/null
+++ b/scripts/build/lint.mk
@@ -0,0 +1,17 @@
+lint_mk_path := $(abspath $(lastword $(MAKEFILE_LIST)))
+lint_mk_dir  := $(dir $(lint_mk_path))
+root_dir     := $(lint_mk_dir)../..
+
+GOLANGCI_VERSION := v1.39.0
+
+LINT_OPTS ?= --timeout 1m0s
+
+##@ Code quality targets
+
+LINTER := $(root_dir)/bin/golangci-lint
+$(LINTER):
+	wget -O - -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(root_dir)/bin $(GOLANGCI_VERSION)
+
+.PHONY: lint
+lint: $(LINTER) ## Run all the linters
+	$(LINTER) --verbose run $(LINT_OPTS) --config $(root_dir)/golangci.yml
diff --git a/scripts/build/test.mk b/scripts/build/test.mk
new file mode 100644
index 0000000..6833ab5
--- /dev/null
+++ b/scripts/build/test.mk
@@ -0,0 +1,33 @@
+
+TEST_OPTS ?=
+TEST_EXTRA_OPTS ?=
+TEST_TAGS ?= $(BUILD_TAGS)
+TEST_PKG_LIST ?= ./...
+
+TEST_COVERAGE_DIR ?= build/coverage
+TEST_COVERAGE_PROFILE := $(TEST_COVERAGE_DIR)/coverage.out
+TEST_COVERAGE_REPORT := $(TEST_COVERAGE_DIR)/coverage.html
+TEST_COVERAGE_PKG_LIST ?= $(TEST_PKG_LIST)
+TEST_COVERAGE_OPTS ?= -covermode=atomic -coverpkg=./...
+TEST_COVERAGE_EXTRA_OPTS ?=
+
+##@ Test targets
+
+.PHONY: test
+test: ## Run all the unit tests
+	go test $(TEST_OPTS) $(TEST_EXTRA_OPTS) -tags "$(TEST_TAGS)" $(TEST_PKG_LIST)
+
+.PHONY: test-race
+test-race: TEST_OPTS=-race
+test-race: test  ## Run all the unit tests with race detector on
+
+.PHONY: test-coverage
+test-coverage: ## Run all the unit tests with coverage analysis on
+	mkdir -p "$(shell dirname "$(TEST_COVERAGE_PROFILE)")"
+	go test $(TEST_COVERAGE_OPTS) $(TEST_COVERAGE_EXTRA_OPTS) -coverprofile="$(TEST_COVERAGE_PROFILE)" -tags "$(TEST_TAGS)" $(TEST_COVERAGE_PKG_LIST)
+	go tool cover -html="$(TEST_COVERAGE_PROFILE)" -o "$(TEST_COVERAGE_REPORT)"
+	@echo "Test coverage report has been saved to $(TEST_COVERAGE_REPORT)"
+
+.PHONY: test-clean
+test-clean::  ## Clean all test artifacts
+	rm -rf $(TEST_COVERAGE_DIR)
\ No newline at end of file