github.com/gohugoio/hugo@v0.88.1/magefile.go (about)

     1  // +build mage
     2  
     3  package main
     4  
     5  import (
     6  	"bytes"
     7  	"errors"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"os"
    11  	"path"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  	"sync"
    16  	"time"
    17  
    18  	"github.com/gohugoio/hugo/codegen"
    19  	"github.com/gohugoio/hugo/resources/page/page_generate"
    20  
    21  	"github.com/magefile/mage/mg"
    22  	"github.com/magefile/mage/sh"
    23  )
    24  
    25  const (
    26  	packageName  = "github.com/gohugoio/hugo"
    27  	noGitLdflags = "-X $PACKAGE/common/hugo.buildDate=$BUILD_DATE"
    28  )
    29  
    30  var ldflags = "-X $PACKAGE/common/hugo.commitHash=$COMMIT_HASH -X $PACKAGE/common/hugo.buildDate=$BUILD_DATE"
    31  
    32  // allow user to override go executable by running as GOEXE=xxx make ... on unix-like systems
    33  var goexe = "go"
    34  
    35  func init() {
    36  	if exe := os.Getenv("GOEXE"); exe != "" {
    37  		goexe = exe
    38  	}
    39  
    40  	// We want to use Go 1.11 modules even if the source lives inside GOPATH.
    41  	// The default is "auto".
    42  	os.Setenv("GO111MODULE", "on")
    43  }
    44  
    45  func runWith(env map[string]string, cmd string, inArgs ...interface{}) error {
    46  	s := argsToStrings(inArgs...)
    47  	return sh.RunWith(env, cmd, s...)
    48  }
    49  
    50  // Build hugo binary
    51  func Hugo() error {
    52  	return runWith(flagEnv(), goexe, "build", "-ldflags", ldflags, buildFlags(), "-tags", buildTags(), packageName)
    53  }
    54  
    55  // Build hugo binary with race detector enabled
    56  func HugoRace() error {
    57  	return runWith(flagEnv(), goexe, "build", "-race", "-ldflags", ldflags, buildFlags(), "-tags", buildTags(), packageName)
    58  }
    59  
    60  // Install hugo binary
    61  func Install() error {
    62  	return runWith(flagEnv(), goexe, "install", "-ldflags", ldflags, buildFlags(), "-tags", buildTags(), packageName)
    63  }
    64  
    65  // Uninstall hugo binary
    66  func Uninstall() error {
    67  	return sh.Run(goexe, "clean", "-i", packageName)
    68  }
    69  
    70  func flagEnv() map[string]string {
    71  	hash, _ := sh.Output("git", "rev-parse", "--short", "HEAD")
    72  	return map[string]string{
    73  		"PACKAGE":     packageName,
    74  		"COMMIT_HASH": hash,
    75  		"BUILD_DATE":  time.Now().Format("2006-01-02T15:04:05Z0700"),
    76  	}
    77  }
    78  
    79  // Generate autogen packages
    80  func Generate() error {
    81  	generatorPackages := []string{
    82  		"tpl/tplimpl/embedded/generate",
    83  		//"resources/page/generate",
    84  	}
    85  
    86  	for _, pkg := range generatorPackages {
    87  		if err := runWith(flagEnv(), goexe, "generate", path.Join(packageName, pkg)); err != nil {
    88  			return err
    89  		}
    90  	}
    91  
    92  	dir, _ := os.Getwd()
    93  	c := codegen.NewInspector(dir)
    94  
    95  	if err := page_generate.Generate(c); err != nil {
    96  		return err
    97  	}
    98  
    99  	goFmtPatterns := []string{
   100  		// TODO(bep) check: stat ./resources/page/*autogen*: no such file or directory
   101  		"./resources/page/page_marshaljson.autogen.go",
   102  		"./resources/page/page_wrappers.autogen.go",
   103  		"./resources/page/zero_file.autogen.go",
   104  	}
   105  
   106  	for _, pattern := range goFmtPatterns {
   107  		if err := sh.Run("gofmt", "-w", filepath.FromSlash(pattern)); err != nil {
   108  			return err
   109  		}
   110  	}
   111  
   112  	return nil
   113  }
   114  
   115  // Generate docs helper
   116  func GenDocsHelper() error {
   117  	return runCmd(flagEnv(), goexe, "run", "-tags", buildTags(), "main.go", "gen", "docshelper")
   118  }
   119  
   120  // Build hugo without git info
   121  func HugoNoGitInfo() error {
   122  	ldflags = noGitLdflags
   123  	return Hugo()
   124  }
   125  
   126  var docker = sh.RunCmd("docker")
   127  
   128  // Build hugo Docker container
   129  func Docker() error {
   130  	if err := docker("build", "-t", "hugo", "."); err != nil {
   131  		return err
   132  	}
   133  	// yes ignore errors here
   134  	docker("rm", "-f", "hugo-build")
   135  	if err := docker("run", "--name", "hugo-build", "hugo ls /go/bin"); err != nil {
   136  		return err
   137  	}
   138  	if err := docker("cp", "hugo-build:/go/bin/hugo", "."); err != nil {
   139  		return err
   140  	}
   141  	return docker("rm", "hugo-build")
   142  }
   143  
   144  // Run tests and linters
   145  func Check() {
   146  	if strings.Contains(runtime.Version(), "1.8") {
   147  		// Go 1.8 doesn't play along with go test ./... and /vendor.
   148  		// We could fix that, but that would take time.
   149  		fmt.Printf("Skip Check on %s\n", runtime.Version())
   150  		return
   151  	}
   152  
   153  	if runtime.GOARCH == "amd64" && runtime.GOOS != "darwin" {
   154  		mg.Deps(Test386)
   155  	} else {
   156  		fmt.Printf("Skip Test386 on %s and/or %s\n", runtime.GOARCH, runtime.GOOS)
   157  	}
   158  
   159  	mg.Deps(Fmt, Vet)
   160  
   161  	// don't run two tests in parallel, they saturate the CPUs anyway, and running two
   162  	// causes memory issues in CI.
   163  	mg.Deps(TestRace)
   164  }
   165  
   166  func testGoFlags() string {
   167  	if isCI() {
   168  		return ""
   169  	}
   170  
   171  	return "-test.short"
   172  }
   173  
   174  // Run tests in 32-bit mode
   175  // Note that we don't run with the extended tag. Currently not supported in 32 bit.
   176  func Test386() error {
   177  	env := map[string]string{"GOARCH": "386", "GOFLAGS": testGoFlags()}
   178  	return runCmd(env, goexe, "test", "./...")
   179  }
   180  
   181  // Run tests
   182  func Test() error {
   183  	env := map[string]string{"GOFLAGS": testGoFlags()}
   184  	return runCmd(env, goexe, "test", "./...", buildFlags(), "-tags", buildTags())
   185  }
   186  
   187  // Run tests with race detector
   188  func TestRace() error {
   189  	env := map[string]string{"GOFLAGS": testGoFlags()}
   190  	return runCmd(env, goexe, "test", "-race", "./...", buildFlags(), "-tags", buildTags())
   191  }
   192  
   193  // Run gofmt linter
   194  func Fmt() error {
   195  	if !isGoLatest() {
   196  		return nil
   197  	}
   198  	pkgs, err := hugoPackages()
   199  	if err != nil {
   200  		return err
   201  	}
   202  	failed := false
   203  	first := true
   204  	for _, pkg := range pkgs {
   205  		files, err := filepath.Glob(filepath.Join(pkg, "*.go"))
   206  		if err != nil {
   207  			return nil
   208  		}
   209  		for _, f := range files {
   210  			// gofmt doesn't exit with non-zero when it finds unformatted code
   211  			// so we have to explicitly look for output, and if we find any, we
   212  			// should fail this target.
   213  			s, err := sh.Output("gofmt", "-l", f)
   214  			if err != nil {
   215  				fmt.Printf("ERROR: running gofmt on %q: %v\n", f, err)
   216  				failed = true
   217  			}
   218  			if s != "" {
   219  				if first {
   220  					fmt.Println("The following files are not gofmt'ed:")
   221  					first = false
   222  				}
   223  				failed = true
   224  				fmt.Println(s)
   225  			}
   226  		}
   227  	}
   228  	if failed {
   229  		return errors.New("improperly formatted go files")
   230  	}
   231  	return nil
   232  }
   233  
   234  var (
   235  	pkgPrefixLen = len("github.com/gohugoio/hugo")
   236  	pkgs         []string
   237  	pkgsInit     sync.Once
   238  )
   239  
   240  func hugoPackages() ([]string, error) {
   241  	var err error
   242  	pkgsInit.Do(func() {
   243  		var s string
   244  		s, err = sh.Output(goexe, "list", "./...")
   245  		if err != nil {
   246  			return
   247  		}
   248  		pkgs = strings.Split(s, "\n")
   249  		for i := range pkgs {
   250  			pkgs[i] = "." + pkgs[i][pkgPrefixLen:]
   251  		}
   252  	})
   253  	return pkgs, err
   254  }
   255  
   256  // Run golint linter
   257  func Lint() error {
   258  	pkgs, err := hugoPackages()
   259  	if err != nil {
   260  		return err
   261  	}
   262  	failed := false
   263  	for _, pkg := range pkgs {
   264  		// We don't actually want to fail this target if we find golint errors,
   265  		// so we don't pass -set_exit_status, but we still print out any failures.
   266  		if _, err := sh.Exec(nil, os.Stderr, nil, "golint", pkg); err != nil {
   267  			fmt.Printf("ERROR: running go lint on %q: %v\n", pkg, err)
   268  			failed = true
   269  		}
   270  	}
   271  	if failed {
   272  		return errors.New("errors running golint")
   273  	}
   274  	return nil
   275  }
   276  
   277  //  Run go vet linter
   278  func Vet() error {
   279  	if err := sh.Run(goexe, "vet", "./..."); err != nil {
   280  		return fmt.Errorf("error running go vet: %v", err)
   281  	}
   282  	return nil
   283  }
   284  
   285  // Generate test coverage report
   286  func TestCoverHTML() error {
   287  	const (
   288  		coverAll = "coverage-all.out"
   289  		cover    = "coverage.out"
   290  	)
   291  	f, err := os.Create(coverAll)
   292  	if err != nil {
   293  		return err
   294  	}
   295  	defer f.Close()
   296  	if _, err := f.Write([]byte("mode: count")); err != nil {
   297  		return err
   298  	}
   299  	pkgs, err := hugoPackages()
   300  	if err != nil {
   301  		return err
   302  	}
   303  	for _, pkg := range pkgs {
   304  		if err := sh.Run(goexe, "test", "-coverprofile="+cover, "-covermode=count", pkg); err != nil {
   305  			return err
   306  		}
   307  		b, err := ioutil.ReadFile(cover)
   308  		if err != nil {
   309  			if os.IsNotExist(err) {
   310  				continue
   311  			}
   312  			return err
   313  		}
   314  		idx := bytes.Index(b, []byte{'\n'})
   315  		b = b[idx+1:]
   316  		if _, err := f.Write(b); err != nil {
   317  			return err
   318  		}
   319  	}
   320  	if err := f.Close(); err != nil {
   321  		return err
   322  	}
   323  	return sh.Run(goexe, "tool", "cover", "-html="+coverAll)
   324  }
   325  
   326  func runCmd(env map[string]string, cmd string, args ...interface{}) error {
   327  	if mg.Verbose() {
   328  		return runWith(env, cmd, args...)
   329  	}
   330  	output, err := sh.OutputWith(env, cmd, argsToStrings(args...)...)
   331  	if err != nil {
   332  		fmt.Fprint(os.Stderr, output)
   333  	}
   334  
   335  	return err
   336  }
   337  
   338  func isGoLatest() bool {
   339  	return strings.Contains(runtime.Version(), "1.14")
   340  }
   341  
   342  func isCI() bool {
   343  	return os.Getenv("CI") != ""
   344  }
   345  
   346  func buildFlags() []string {
   347  	if runtime.GOOS == "windows" {
   348  		return []string{"-buildmode", "exe"}
   349  	}
   350  	return nil
   351  }
   352  
   353  func buildTags() string {
   354  	// To build the extended Hugo SCSS/SASS enabled version, build with
   355  	// HUGO_BUILD_TAGS=extended mage install etc.
   356  	// To build without `hugo deploy` for smaller binary, use HUGO_BUILD_TAGS=nodeploy
   357  	if envtags := os.Getenv("HUGO_BUILD_TAGS"); envtags != "" {
   358  		return envtags
   359  	}
   360  	return "none"
   361  }
   362  
   363  func argsToStrings(v ...interface{}) []string {
   364  	var args []string
   365  	for _, arg := range v {
   366  		switch v := arg.(type) {
   367  		case string:
   368  			if v != "" {
   369  				args = append(args, v)
   370  			}
   371  		case []string:
   372  			if v != nil {
   373  				args = append(args, v...)
   374  			}
   375  		default:
   376  			panic("invalid type")
   377  		}
   378  	}
   379  
   380  	return args
   381  }