2020-09-03 15:13:19 +00:00
|
|
|
// Vikunja is a to-do list application to facilitate your life.
|
2023-09-01 08:32:28 +02:00
|
|
|
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
2020-09-03 15:13:19 +00:00
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
2020-12-23 16:41:52 +01:00
|
|
|
// it under the terms of the GNU Affero General Public Licensee as published by
|
2020-09-03 15:13:19 +00:00
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2020-12-23 16:41:52 +01:00
|
|
|
// GNU Affero General Public Licensee for more details.
|
2020-09-03 15:13:19 +00:00
|
|
|
//
|
2020-12-23 16:41:52 +01:00
|
|
|
// You should have received a copy of the GNU Affero General Public Licensee
|
2020-09-03 15:13:19 +00:00
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
2022-01-23 12:59:43 +00:00
|
|
|
//go:build mage
|
2020-09-03 15:13:19 +00:00
|
|
|
// +build mage
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"crypto/sha256"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
2020-09-04 10:15:33 +02:00
|
|
|
"time"
|
2020-12-17 13:44:04 +00:00
|
|
|
|
2024-03-02 08:47:10 +00:00
|
|
|
"github.com/iancoleman/strcase"
|
|
|
|
|
2020-12-17 13:44:04 +00:00
|
|
|
"github.com/magefile/mage/mg"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"gopkg.in/yaml.v3"
|
2020-09-03 15:13:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
PACKAGE = `code.vikunja.io/api`
|
|
|
|
DIST = `dist`
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
Goflags = []string{
|
|
|
|
"-v",
|
|
|
|
}
|
|
|
|
Executable = "vikunja"
|
|
|
|
Ldflags = ""
|
|
|
|
Tags = ""
|
|
|
|
VersionNumber = "dev"
|
2021-02-10 19:56:51 +01:00
|
|
|
Version = "unstable" // This holds the built version, unstable by default, when building from a tag or release branch, their name
|
2020-09-03 15:13:19 +00:00
|
|
|
BinLocation = ""
|
2021-02-10 19:56:51 +01:00
|
|
|
PkgVersion = "unstable"
|
2020-09-03 15:13:19 +00:00
|
|
|
ApiPackages = []string{}
|
|
|
|
RootPath = ""
|
|
|
|
GoFiles = []string{}
|
|
|
|
|
|
|
|
// Aliases are mage aliases of targets
|
|
|
|
Aliases = map[string]interface{}{
|
2021-02-07 21:05:09 +00:00
|
|
|
"build": Build.Build,
|
|
|
|
"do-the-swag": DoTheSwag,
|
|
|
|
"check:got-swag": Check.GotSwag,
|
2024-02-09 19:09:19 +01:00
|
|
|
"release": Release.Release,
|
2021-02-07 21:05:09 +00:00
|
|
|
"release:os-package": Release.OsPackage,
|
|
|
|
"dev:make-migration": Dev.MakeMigration,
|
|
|
|
"dev:make-event": Dev.MakeEvent,
|
|
|
|
"dev:make-listener": Dev.MakeListener,
|
|
|
|
"dev:make-notification": Dev.MakeNotification,
|
|
|
|
"generate-docs": GenerateDocs,
|
2024-02-14 15:05:11 +01:00
|
|
|
"lint": Check.Golangci,
|
|
|
|
"lint:fix": Check.GolangciFix,
|
2020-09-03 15:13:19 +00:00
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2021-09-24 20:03:38 +02:00
|
|
|
func runCmdWithOutput(name string, arg ...string) (output []byte, err error) {
|
|
|
|
cmd := exec.Command(name, arg...)
|
|
|
|
output, err = cmd.Output()
|
|
|
|
if err != nil {
|
2022-08-03 17:11:17 +02:00
|
|
|
if ee, is := err.(*exec.ExitError); is {
|
|
|
|
return nil, fmt.Errorf("error running command: %s, %s", string(ee.Stderr), err)
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("error running command: %s", err)
|
2021-09-24 20:03:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return output, nil
|
|
|
|
}
|
|
|
|
|
2020-09-03 15:13:19 +00:00
|
|
|
func setVersion() {
|
2021-09-24 20:03:38 +02:00
|
|
|
version, err := runCmdWithOutput("git", "describe", "--tags", "--always", "--abbrev=10")
|
2020-09-03 15:13:19 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error getting version: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
VersionNumber = strings.Trim(string(version), "\n")
|
|
|
|
VersionNumber = strings.Replace(VersionNumber, "-g", "-", 1)
|
|
|
|
|
|
|
|
if os.Getenv("DRONE_TAG") != "" {
|
|
|
|
Version = os.Getenv("DRONE_TAG")
|
|
|
|
} else if os.Getenv("DRONE_BRANCH") != "" {
|
|
|
|
Version = strings.Replace(os.Getenv("DRONE_BRANCH"), "release/v", "", 1)
|
|
|
|
}
|
2021-02-13 16:32:26 +01:00
|
|
|
|
|
|
|
if Version == "main" {
|
|
|
|
Version = "unstable"
|
|
|
|
}
|
2020-09-03 15:13:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func setBinLocation() {
|
|
|
|
if os.Getenv("DRONE_WORKSPACE") != "" {
|
|
|
|
BinLocation = DIST + `/binaries/` + Executable + `-` + Version + `-linux-amd64`
|
|
|
|
} else {
|
|
|
|
BinLocation = Executable
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setPkgVersion() {
|
2021-02-10 19:56:51 +01:00
|
|
|
if Version == "unstable" {
|
2020-09-03 15:13:19 +00:00
|
|
|
PkgVersion = VersionNumber
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setExecutable() {
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
Executable += ".exe"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setApiPackages() {
|
2021-09-24 20:03:38 +02:00
|
|
|
pkgs, err := runCmdWithOutput("go", "list", "all")
|
2020-09-03 15:13:19 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error getting packages: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
for _, p := range strings.Split(string(pkgs), "\n") {
|
|
|
|
if strings.Contains(p, "code.vikunja.io/api") && !strings.Contains(p, "code.vikunja.io/api/pkg/integrations") {
|
|
|
|
ApiPackages = append(ApiPackages, p)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func setRootPath() {
|
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error getting pwd: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
if err := os.Setenv("VIKUNJA_SERVICE_ROOTPATH", pwd); err != nil {
|
|
|
|
fmt.Printf("Error setting root path: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
RootPath = pwd
|
|
|
|
}
|
|
|
|
|
|
|
|
func setGoFiles() {
|
|
|
|
// GOFILES := $(shell find . -name "*.go" -type f ! -path "*/bindata.go")
|
2024-02-27 18:06:34 +00:00
|
|
|
files, err := runCmdWithOutput("find", "./pkg", "-name", "*.go", "-type", "f", "!", "-path", "*/bindata.go")
|
2020-09-03 15:13:19 +00:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error getting go files: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
for _, f := range strings.Split(string(files), "\n") {
|
|
|
|
if strings.HasSuffix(f, ".go") {
|
|
|
|
GoFiles = append(GoFiles, RootPath+strings.TrimLeft(f, "."))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 22:14:30 +02:00
|
|
|
// Some variables can always get initialized, so we do just that.
|
2020-09-03 15:13:19 +00:00
|
|
|
func init() {
|
2020-09-03 22:14:30 +02:00
|
|
|
setExecutable()
|
|
|
|
setRootPath()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Some variables have external dependencies (like git) which may not always be available.
|
|
|
|
func initVars() {
|
2020-09-03 15:13:19 +00:00
|
|
|
Tags = os.Getenv("TAGS")
|
|
|
|
setVersion()
|
|
|
|
setBinLocation()
|
|
|
|
setPkgVersion()
|
|
|
|
setGoFiles()
|
|
|
|
Ldflags = `-X "` + PACKAGE + `/pkg/version.Version=` + VersionNumber + `" -X "main.Tags=` + Tags + `"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func runAndStreamOutput(cmd string, args ...string) {
|
|
|
|
c := exec.Command(cmd, args...)
|
|
|
|
|
|
|
|
c.Env = os.Environ()
|
|
|
|
c.Dir = RootPath
|
|
|
|
|
|
|
|
fmt.Printf("%s\n\n", c.String())
|
|
|
|
|
|
|
|
stdout, _ := c.StdoutPipe()
|
|
|
|
errbuf := bytes.Buffer{}
|
|
|
|
c.Stderr = &errbuf
|
2023-04-02 18:55:30 +02:00
|
|
|
err := c.Start()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Could not start: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2020-09-03 15:13:19 +00:00
|
|
|
|
|
|
|
reader := bufio.NewReader(stdout)
|
|
|
|
line, err := reader.ReadString('\n')
|
|
|
|
for err == nil {
|
|
|
|
fmt.Print(line)
|
|
|
|
line, err = reader.ReadString('\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := c.Wait(); err != nil {
|
|
|
|
fmt.Printf(errbuf.String())
|
|
|
|
fmt.Printf("Error: %s\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Will check if the tool exists and if not install it from the provided import path
|
|
|
|
// If any errors occur, it will exit with a status code of 1.
|
|
|
|
func checkAndInstallGoTool(tool, importPath string) {
|
|
|
|
if err := exec.Command(tool).Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
|
|
|
|
fmt.Printf("%s not installed, installing %s...\n", tool, importPath)
|
|
|
|
if err := exec.Command("go", "install", Goflags[0], importPath).Run(); err != nil {
|
|
|
|
fmt.Printf("Error installing %s\n", tool)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
fmt.Println("Installed.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculates a hash of a file
|
|
|
|
func calculateSha256FileHash(path string) (hash string, err error) {
|
|
|
|
f, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
h := sha256.New()
|
|
|
|
if _, err := io.Copy(h, f); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy the src file to dst. Any existing file will be overwritten and will not
|
|
|
|
// copy file attributes.
|
|
|
|
func copyFile(src, dst string) error {
|
|
|
|
in, err := os.Open(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer in.Close()
|
|
|
|
|
|
|
|
out, err := os.Create(dst)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer out.Close()
|
|
|
|
|
|
|
|
_, err = io.Copy(out, in)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
si, err := os.Stat(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Chmod(dst, si.Mode()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return out.Close()
|
|
|
|
}
|
|
|
|
|
2020-09-03 20:42:26 +02:00
|
|
|
// os.Rename has issues with moving files between docker volumes.
|
|
|
|
// Because of this limitaion, it fails in drone.
|
|
|
|
// Source: https://gist.github.com/var23rav/23ae5d0d4d830aff886c3c970b8f6c6b
|
2020-09-04 14:00:54 +02:00
|
|
|
func moveFile(src, dst string) error {
|
|
|
|
inputFile, err := os.Open(src)
|
|
|
|
defer inputFile.Close()
|
2020-09-03 20:42:26 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't open source file: %s", err)
|
|
|
|
}
|
2020-09-04 14:00:54 +02:00
|
|
|
|
|
|
|
outputFile, err := os.Create(dst)
|
|
|
|
defer outputFile.Close()
|
2020-09-03 20:42:26 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("couldn't open dest file: %s", err)
|
|
|
|
}
|
2020-09-04 14:00:54 +02:00
|
|
|
|
2020-09-03 20:42:26 +02:00
|
|
|
_, err = io.Copy(outputFile, inputFile)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("writing to output file failed: %s", err)
|
|
|
|
}
|
2020-09-04 14:00:54 +02:00
|
|
|
|
|
|
|
// Make sure to copy copy the permissions of the original file as well
|
|
|
|
si, err := os.Stat(src)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := os.Chmod(dst, si.Mode()); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-09-03 20:42:26 +02:00
|
|
|
// The copy was successful, so now delete the original file
|
2020-09-04 14:00:54 +02:00
|
|
|
err = os.Remove(src)
|
2020-09-03 20:42:26 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed removing original file: %s", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-02-02 22:48:37 +00:00
|
|
|
func appendToFile(filename, content string) error {
|
|
|
|
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
_, err = f.WriteString(content)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
const InfoColor = "\033[1;32m%s\033[0m"
|
|
|
|
|
|
|
|
func printSuccess(text string, args ...interface{}) {
|
|
|
|
text = fmt.Sprintf(text, args...)
|
|
|
|
fmt.Printf(InfoColor+"\n", text)
|
|
|
|
}
|
|
|
|
|
2020-09-03 15:13:19 +00:00
|
|
|
// Formats the code using go fmt
|
|
|
|
func Fmt() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
args := append([]string{"-s", "-w"}, GoFiles...)
|
|
|
|
runAndStreamOutput("gofmt", args...)
|
|
|
|
}
|
|
|
|
|
2023-04-02 18:55:30 +02:00
|
|
|
const swaggerDocsFolderLocation = `./pkg/swagger/`
|
|
|
|
|
2020-09-03 15:13:19 +00:00
|
|
|
// Generates the swagger docs from the code annotations
|
|
|
|
func DoTheSwag() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2023-04-02 16:52:54 +02:00
|
|
|
|
2020-09-03 15:13:19 +00:00
|
|
|
checkAndInstallGoTool("swag", "github.com/swaggo/swag/cmd/swag")
|
|
|
|
runAndStreamOutput("swag", "init", "-g", "./pkg/routes/routes.go", "--parseDependency", "-d", RootPath, "-o", RootPath+"/pkg/swagger")
|
|
|
|
}
|
|
|
|
|
|
|
|
type Test mg.Namespace
|
|
|
|
|
|
|
|
// Runs all tests except integration tests
|
|
|
|
func (Test) Unit() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2021-10-02 15:40:56 +02:00
|
|
|
setApiPackages()
|
2020-09-03 15:13:19 +00:00
|
|
|
// We run everything sequentially and not in parallel to prevent issues with real test databases
|
2022-08-02 22:49:48 +02:00
|
|
|
args := append([]string{"test", Goflags[0], "-p", "1", "-coverprofile", "cover.out", "-timeout", "45m"}, ApiPackages...)
|
2020-09-03 15:13:19 +00:00
|
|
|
runAndStreamOutput("go", args...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs the tests and builds the coverage html file from coverage output
|
|
|
|
func (Test) Coverage() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
mg.Deps(Test.Unit)
|
|
|
|
runAndStreamOutput("go", "tool", "cover", "-html=cover.out", "-o", "cover.html")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs the integration tests
|
|
|
|
func (Test) Integration() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
// We run everything sequentially and not in parallel to prevent issues with real test databases
|
2022-08-02 22:49:48 +02:00
|
|
|
runAndStreamOutput("go", "test", Goflags[0], "-p", "1", "-timeout", "45m", PACKAGE+"/pkg/integrations")
|
2020-09-03 15:13:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type Check mg.Namespace
|
|
|
|
|
|
|
|
// Checks if the swagger docs need to be re-generated from the code annotations
|
|
|
|
func (Check) GotSwag() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
// The check is pretty cheaply done: We take the hash of the swagger.json file, generate the docs,
|
|
|
|
// hash the file again and compare the two hashes to see if anything changed. If that's the case,
|
|
|
|
// regenerating the docs is necessary.
|
|
|
|
// swag is not capable of just outputting the generated docs to stdout, therefore we need to do it this way.
|
|
|
|
// Another drawback of this is obviously it will only work once - we're not resetting the newly generated
|
|
|
|
// docs after the check. This behaviour is good enough for ci though.
|
|
|
|
oldHash, err := calculateSha256FileHash(RootPath + "/pkg/swagger/swagger.json")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error getting old hash of the swagger docs: %s", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
DoTheSwag()
|
|
|
|
|
|
|
|
newHash, err := calculateSha256FileHash(RootPath + "/pkg/swagger/swagger.json")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Error getting new hash of the swagger docs: %s", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
if oldHash != newHash {
|
|
|
|
fmt.Println("Swagger docs are not up to date.")
|
|
|
|
fmt.Println("Please run 'mage do-the-swag' and commit the result.")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:10:03 +00:00
|
|
|
func checkGolangCiLintInstalled() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-10-11 20:10:03 +00:00
|
|
|
if err := exec.Command("golangci-lint").Run(); err != nil && strings.Contains(err.Error(), "executable file not found") {
|
|
|
|
fmt.Println("Please manually install golangci-lint by running")
|
2024-03-10 13:47:19 +00:00
|
|
|
fmt.Println("curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.56.2")
|
2020-09-03 15:13:19 +00:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:10:03 +00:00
|
|
|
func (Check) Golangci() {
|
|
|
|
checkGolangCiLintInstalled()
|
|
|
|
runAndStreamOutput("golangci-lint", "run")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (Check) GolangciFix() {
|
|
|
|
checkGolangCiLintInstalled()
|
|
|
|
runAndStreamOutput("golangci-lint", "run", "--fix")
|
2020-09-03 15:13:19 +00:00
|
|
|
}
|
|
|
|
|
2021-03-28 17:24:56 +02:00
|
|
|
// Runs golangci and the swagger test in parralel
|
2020-09-03 15:13:19 +00:00
|
|
|
func (Check) All() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
mg.Deps(
|
2020-10-11 20:10:03 +00:00
|
|
|
Check.Golangci,
|
2020-09-03 15:13:19 +00:00
|
|
|
Check.GotSwag,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
type Build mg.Namespace
|
|
|
|
|
|
|
|
// Cleans all build, executable and bindata files
|
|
|
|
func (Build) Clean() error {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
if err := exec.Command("go", "clean", "./...").Run(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.Remove(Executable); err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.RemoveAll(DIST); err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.RemoveAll(BinLocation); err != nil && !os.IsNotExist(err) {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Builds a vikunja binary, ready to run
|
|
|
|
func (Build) Build() {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
runAndStreamOutput("go", "build", Goflags[0], "-tags", Tags, "-ldflags", "-s -w "+Ldflags, "-o", Executable)
|
|
|
|
}
|
|
|
|
|
|
|
|
type Release mg.Namespace
|
|
|
|
|
|
|
|
// Runs all steps in the right order to create release packages for various platforms
|
|
|
|
func (Release) Release(ctx context.Context) error {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2021-02-07 21:05:09 +00:00
|
|
|
mg.Deps(Release.Dirs)
|
2020-09-03 15:13:19 +00:00
|
|
|
|
|
|
|
// Run compiling in parallel to speed it up
|
|
|
|
errs, _ := errgroup.WithContext(ctx)
|
|
|
|
errs.Go((Release{}).Windows)
|
|
|
|
errs.Go((Release{}).Linux)
|
|
|
|
errs.Go((Release{}).Darwin)
|
|
|
|
if err := errs.Wait(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := (Release{}).Compress(ctx); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := (Release{}).Copy(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := (Release{}).Check(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := (Release{}).OsPackage(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := (Release{}).Zip(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates all directories needed to release vikunja
|
|
|
|
func (Release) Dirs() error {
|
|
|
|
for _, d := range []string{"binaries", "release", "zip"} {
|
|
|
|
if err := os.MkdirAll(RootPath+"/"+DIST+"/"+d, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func runXgo(targets string) error {
|
2020-09-03 22:14:30 +02:00
|
|
|
mg.Deps(initVars)
|
2020-09-03 15:13:19 +00:00
|
|
|
checkAndInstallGoTool("xgo", "src.techknowlogick.com/xgo")
|
2020-09-03 21:22:41 +02:00
|
|
|
|
|
|
|
extraLdflags := `-linkmode external -extldflags "-static" `
|
|
|
|
|
|
|
|
// See https://github.com/techknowlogick/xgo/issues/79
|
|
|
|
if strings.HasPrefix(targets, "darwin") {
|
|
|
|
extraLdflags = ""
|
|
|
|
}
|
|
|
|
|
2020-09-03 15:13:19 +00:00
|
|
|
runAndStreamOutput("xgo",
|
|
|
|
"-dest", RootPath+"/"+DIST+"/binaries",
|
|
|
|
"-tags", "netgo "+Tags,
|
2020-09-03 21:22:41 +02:00
|
|
|
"-ldflags", extraLdflags+Ldflags,
|
2020-09-03 15:13:19 +00:00
|
|
|
"-targets", targets,
|
|
|
|
"-out", Executable+"-"+Version,
|
|
|
|
RootPath)
|
|
|
|
if os.Getenv("DRONE_WORKSPACE") != "" {
|
|
|
|
return filepath.Walk("/build/", func(path string, info os.FileInfo, err error) error {
|
2020-09-03 18:08:57 +02:00
|
|
|
// Skip directories
|
|
|
|
if info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-09-03 20:42:26 +02:00
|
|
|
return moveFile(path, RootPath+"/"+DIST+"/binaries/"+info.Name())
|
2020-09-03 15:13:19 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Builds binaries for windows
|
|
|
|
func (Release) Windows() error {
|
|
|
|
return runXgo("windows/*")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Builds binaries for linux
|
|
|
|
func (Release) Linux() error {
|
|
|
|
return runXgo("linux/*")
|
|
|
|
}
|
|
|
|
|
|
|
|
// Builds binaries for darwin
|
|
|
|
func (Release) Darwin() error {
|
2021-02-18 21:35:45 +01:00
|
|
|
return runXgo("darwin-10.15/*")
|
2020-09-03 15:13:19 +00:00
|
|
|
}
|
|
|
|
|
2022-11-12 14:29:57 +01:00
|
|
|
func (Release) Xgo(target string) error {
|
|
|
|
parts := strings.Split(target, "/")
|
|
|
|
if len(parts) < 2 {
|
|
|
|
return fmt.Errorf("invalid target")
|
|
|
|
}
|
|
|
|
|
|
|
|
variant := ""
|
|
|
|
if len(parts) > 2 && parts[2] != "" {
|
|
|
|
variant = "-" + strings.ReplaceAll(parts[2], "v", "")
|
|
|
|
}
|
|
|
|
|
|
|
|
return runXgo(parts[0] + "/" + parts[1] + variant)
|
|
|
|
}
|
|
|
|
|
2020-09-03 15:13:19 +00:00
|
|
|
// Compresses the built binaries in dist/binaries/ to reduce their filesize
|
|
|
|
func (Release) Compress(ctx context.Context) error {
|
|
|
|
// $(foreach file,$(filter-out $(wildcard $(wildcard $(DIST)/binaries/$(EXECUTABLE)-*mips*)),$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*)), upx -9 $(file);)
|
|
|
|
|
|
|
|
errs, _ := errgroup.WithContext(ctx)
|
|
|
|
|
|
|
|
filepath.Walk(RootPath+"/"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error {
|
|
|
|
// Only executable files
|
|
|
|
if !strings.Contains(info.Name(), Executable) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
// No mips or s390x for you today
|
2022-09-07 16:38:43 +02:00
|
|
|
if strings.Contains(info.Name(), "mips") ||
|
|
|
|
strings.Contains(info.Name(), "s390x") ||
|
|
|
|
strings.Contains(info.Name(), "riscv64") { // not supported by upx
|
2020-09-03 15:13:19 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Runs compressing in parallel since upx is single-threaded
|
|
|
|
errs.Go(func() error {
|
2020-09-03 22:45:04 +02:00
|
|
|
runAndStreamOutput("chmod", "+x", path) // Make sure all binaries are executable. Sometimes the CI does weired things and they're not.
|
2020-09-03 15:13:19 +00:00
|
|
|
runAndStreamOutput("upx", "-9", path)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return errs.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copies all built binaries to dist/release/ in preparation for creating the os packages
|
|
|
|
func (Release) Copy() error {
|
|
|
|
return filepath.Walk(RootPath+"/"+DIST+"/binaries/", func(path string, info os.FileInfo, err error) error {
|
|
|
|
// Only executable files
|
|
|
|
if !strings.Contains(info.Name(), Executable) {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return copyFile(path, RootPath+"/"+DIST+"/release/"+info.Name())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates sha256 checksum files for each binary in dist/release/
|
|
|
|
func (Release) Check() error {
|
|
|
|
p := RootPath + "/" + DIST + "/release/"
|
|
|
|
return filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.Create(p + info.Name() + ".sha256")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
hash, err := calculateSha256FileHash(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = f.WriteString(hash + " " + info.Name())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return f.Close()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a folder for each
|
|
|
|
func (Release) OsPackage() error {
|
|
|
|
p := RootPath + "/" + DIST + "/release/"
|
|
|
|
|
|
|
|
// We first put all files in a map to then iterate over it since the walk function would otherwise also iterate
|
|
|
|
// over the newly created files, creating some kind of endless loop.
|
|
|
|
bins := make(map[string]os.FileInfo)
|
|
|
|
if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if strings.Contains(info.Name(), ".sha256") || info.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
bins[path] = info
|
|
|
|
return nil
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for path, info := range bins {
|
|
|
|
folder := p + info.Name() + "-full/"
|
|
|
|
if err := os.Mkdir(folder, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-09-03 20:42:26 +02:00
|
|
|
if err := moveFile(p+info.Name()+".sha256", folder+info.Name()+".sha256"); err != nil {
|
2020-09-03 15:13:19 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-09-03 20:42:26 +02:00
|
|
|
if err := moveFile(path, folder+info.Name()); err != nil {
|
2020-09-03 15:13:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := copyFile(RootPath+"/config.yml.sample", folder+"config.yml.sample"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := copyFile(RootPath+"/LICENSE", folder+"LICENSE"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a zip file from all os-package folders in dist/release
|
|
|
|
func (Release) Zip() error {
|
|
|
|
p := RootPath + "/" + DIST + "/release/"
|
|
|
|
if err := filepath.Walk(p, func(path string, info os.FileInfo, err error) error {
|
|
|
|
if !info.IsDir() || info.Name() == "release" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Zipping %s...\n", info.Name())
|
|
|
|
|
2022-10-02 16:29:10 +02:00
|
|
|
c := exec.Command("zip", "-r", RootPath+"/"+DIST+"/zip/"+info.Name()+".zip", ".", "-i", "*")
|
2020-09-03 15:13:19 +00:00
|
|
|
c.Dir = path
|
|
|
|
out, err := c.Output()
|
|
|
|
fmt.Print(string(out))
|
|
|
|
return err
|
|
|
|
}); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates a debian repo structure
|
|
|
|
func (Release) Reprepro() {
|
2020-10-18 11:41:27 +00:00
|
|
|
mg.Deps(setVersion, setBinLocation)
|
2020-12-18 23:33:23 +01:00
|
|
|
runAndStreamOutput("reprepro_expect", "debian", "includedeb", "buster", RootPath+"/"+DIST+"/os-packages/"+Executable+"_"+strings.ReplaceAll(VersionNumber, "v0", "0")+"_amd64.deb")
|
2020-10-18 11:41:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Creates deb, rpm and apk packages
|
|
|
|
func (Release) Packages() error {
|
|
|
|
mg.Deps(initVars)
|
2020-10-19 07:58:24 +02:00
|
|
|
var err error
|
|
|
|
binpath := "nfpm"
|
|
|
|
err = exec.Command(binpath).Run()
|
|
|
|
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
2022-12-19 15:36:39 +01:00
|
|
|
binpath = "/usr/bin/nfpm"
|
2020-10-19 07:58:24 +02:00
|
|
|
err = exec.Command(binpath).Run()
|
|
|
|
}
|
|
|
|
if err != nil && strings.Contains(err.Error(), "executable file not found") {
|
2020-10-18 11:41:27 +00:00
|
|
|
fmt.Println("Please manually install nfpm by running")
|
|
|
|
fmt.Println("curl -sfL https://install.goreleaser.com/github.com/goreleaser/nfpm.sh | sh -s -- -b $(go env GOPATH)/bin")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
2022-12-19 15:36:39 +01:00
|
|
|
// Because nfpm does not support templating, we replace the values in the config file and restore it after running
|
2020-10-18 11:41:27 +00:00
|
|
|
nfpmConfigPath := RootPath + "/nfpm.yaml"
|
2022-10-01 15:05:12 +00:00
|
|
|
nfpmconfig, err := os.ReadFile(nfpmConfigPath)
|
2020-10-18 11:41:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fixedConfig := strings.ReplaceAll(string(nfpmconfig), "<version>", VersionNumber)
|
|
|
|
fixedConfig = strings.ReplaceAll(fixedConfig, "<binlocation>", BinLocation)
|
2022-10-01 15:05:12 +00:00
|
|
|
if err := os.WriteFile(nfpmConfigPath, []byte(fixedConfig), 0); err != nil {
|
2020-10-18 11:41:27 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
releasePath := RootPath + "/" + DIST + "/os-packages/"
|
|
|
|
if err := os.MkdirAll(releasePath, 0755); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-19 07:58:24 +02:00
|
|
|
runAndStreamOutput(binpath, "pkg", "--packager", "deb", "--target", releasePath)
|
|
|
|
runAndStreamOutput(binpath, "pkg", "--packager", "rpm", "--target", releasePath)
|
|
|
|
runAndStreamOutput(binpath, "pkg", "--packager", "apk", "--target", releasePath)
|
2020-10-18 11:41:27 +00:00
|
|
|
|
2022-10-01 15:05:12 +00:00
|
|
|
return os.WriteFile(nfpmConfigPath, nfpmconfig, 0)
|
2020-09-03 15:13:19 +00:00
|
|
|
}
|
2020-09-04 10:15:33 +02:00
|
|
|
|
|
|
|
type Dev mg.Namespace
|
|
|
|
|
|
|
|
// Creates a new bare db migration skeleton in pkg/migration with the current date
|
2021-02-02 22:48:37 +00:00
|
|
|
func (Dev) MakeMigration() error {
|
2020-09-04 10:15:33 +02:00
|
|
|
|
|
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
fmt.Print("Enter the name of the struct: ")
|
|
|
|
str, _ := reader.ReadString('\n')
|
|
|
|
str = strings.Trim(str, "\n")
|
|
|
|
|
|
|
|
date := time.Now().Format("20060102150405")
|
|
|
|
|
|
|
|
migration := `// Vikunja is a to-do list application to facilitate your life.
|
2023-09-01 08:32:28 +02:00
|
|
|
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
2020-09-04 10:15:33 +02:00
|
|
|
//
|
|
|
|
// This program is free software: you can redistribute it and/or modify
|
2020-12-23 16:41:52 +01:00
|
|
|
// it under the terms of the GNU Affero General Public Licensee as published by
|
2020-09-04 10:15:33 +02:00
|
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
|
|
// (at your option) any later version.
|
|
|
|
//
|
|
|
|
// This program is distributed in the hope that it will be useful,
|
|
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
2020-12-23 16:41:52 +01:00
|
|
|
// GNU Affero General Public Licensee for more details.
|
2020-09-04 10:15:33 +02:00
|
|
|
//
|
2020-12-23 16:41:52 +01:00
|
|
|
// You should have received a copy of the GNU Affero General Public Licensee
|
2020-09-04 10:15:33 +02:00
|
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
package migration
|
|
|
|
|
|
|
|
import (
|
|
|
|
"src.techknowlogick.com/xormigrate"
|
|
|
|
"xorm.io/xorm"
|
|
|
|
)
|
|
|
|
|
|
|
|
type ` + str + date + ` struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
func (` + str + date + `) TableName() string {
|
|
|
|
return "` + str + `"
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
migrations = append(migrations, &xormigrate.Migration{
|
|
|
|
ID: "` + date + `",
|
|
|
|
Description: "",
|
|
|
|
Migrate: func(tx *xorm.Engine) error {
|
|
|
|
return tx.Sync2(` + str + date + `{})
|
|
|
|
},
|
|