github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/magefile.go (about)

     1  //go:build mage
     2  // +build mage
     3  
     4  package main
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    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 github.com/gohugoio/hugo/common/hugo.vendorInfo=mage"
    28  )
    29  
    30  var ldflags = noGitLdflags
    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 ...any) 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 runtime.GOARCH == "amd64" && runtime.GOOS != "darwin" {
   147  		mg.Deps(Test386)
   148  	} else {
   149  		fmt.Printf("Skip Test386 on %s and/or %s\n", runtime.GOARCH, runtime.GOOS)
   150  	}
   151  
   152  	mg.Deps(Fmt, Vet)
   153  
   154  	// don't run two tests in parallel, they saturate the CPUs anyway, and running two
   155  	// causes memory issues in CI.
   156  	mg.Deps(TestRace)
   157  }
   158  
   159  func testGoFlags() string {
   160  	if isCI() {
   161  		return ""
   162  	}
   163  
   164  	return "-timeout=1m"
   165  }
   166  
   167  // Run tests in 32-bit mode
   168  // Note that we don't run with the extended tag. Currently not supported in 32 bit.
   169  func Test386() error {
   170  	env := map[string]string{"GOARCH": "386", "GOFLAGS": testGoFlags()}
   171  	return runCmd(env, goexe, "test", "./...")
   172  }
   173  
   174  // Run tests
   175  func Test() error {
   176  	env := map[string]string{"GOFLAGS": testGoFlags()}
   177  	return runCmd(env, goexe, "test", "./...", buildFlags(), "-tags", buildTags())
   178  }
   179  
   180  // Run tests with race detector
   181  func TestRace() error {
   182  	env := map[string]string{"GOFLAGS": testGoFlags()}
   183  	return runCmd(env, goexe, "test", "-race", "./...", buildFlags(), "-tags", buildTags())
   184  }
   185  
   186  // Run gofmt linter
   187  func Fmt() error {
   188  	if !isGoLatest() {
   189  		return nil
   190  	}
   191  	pkgs, err := hugoPackages()
   192  	if err != nil {
   193  		return err
   194  	}
   195  	failed := false
   196  	first := true
   197  	for _, pkg := range pkgs {
   198  		files, err := filepath.Glob(filepath.Join(pkg, "*.go"))
   199  		if err != nil {
   200  			return nil
   201  		}
   202  		for _, f := range files {
   203  			// gofmt doesn't exit with non-zero when it finds unformatted code
   204  			// so we have to explicitly look for output, and if we find any, we
   205  			// should fail this target.
   206  			s, err := sh.Output("gofmt", "-l", f)
   207  			if err != nil {
   208  				fmt.Printf("ERROR: running gofmt on %q: %v\n", f, err)
   209  				failed = true
   210  			}
   211  			if s != "" {
   212  				if first {
   213  					fmt.Println("The following files are not gofmt'ed:")
   214  					first = false
   215  				}
   216  				failed = true
   217  				fmt.Println(s)
   218  			}
   219  		}
   220  	}
   221  	if failed {
   222  		return errors.New("improperly formatted go files")
   223  	}
   224  	return nil
   225  }
   226  
   227  var (
   228  	pkgPrefixLen = len("github.com/gohugoio/hugo")
   229  	pkgs         []string
   230  	pkgsInit     sync.Once
   231  )
   232  
   233  func hugoPackages() ([]string, error) {
   234  	var err error
   235  	pkgsInit.Do(func() {
   236  		var s string
   237  		s, err = sh.Output(goexe, "list", "./...")
   238  		if err != nil {
   239  			return
   240  		}
   241  		pkgs = strings.Split(s, "\n")
   242  		for i := range pkgs {
   243  			pkgs[i] = "." + pkgs[i][pkgPrefixLen:]
   244  		}
   245  	})
   246  	return pkgs, err
   247  }
   248  
   249  // Run golint linter
   250  func Lint() error {
   251  	pkgs, err := hugoPackages()
   252  	if err != nil {
   253  		return err
   254  	}
   255  	failed := false
   256  	for _, pkg := range pkgs {
   257  		// We don't actually want to fail this target if we find golint errors,
   258  		// so we don't pass -set_exit_status, but we still print out any failures.
   259  		if _, err := sh.Exec(nil, os.Stderr, nil, "golint", pkg); err != nil {
   260  			fmt.Printf("ERROR: running go lint on %q: %v\n", pkg, err)
   261  			failed = true
   262  		}
   263  	}
   264  	if failed {
   265  		return errors.New("errors running golint")
   266  	}
   267  	return nil
   268  }
   269  
   270  // Run go vet linter
   271  func Vet() error {
   272  	if err := sh.Run(goexe, "vet", "./..."); err != nil {
   273  		return fmt.Errorf("error running go vet: %v", err)
   274  	}
   275  	return nil
   276  }
   277  
   278  // Generate test coverage report
   279  func TestCoverHTML() error {
   280  	const (
   281  		coverAll = "coverage-all.out"
   282  		cover    = "coverage.out"
   283  	)
   284  	f, err := os.Create(coverAll)
   285  	if err != nil {
   286  		return err
   287  	}
   288  	defer f.Close()
   289  	if _, err := f.Write([]byte("mode: count")); err != nil {
   290  		return err
   291  	}
   292  	pkgs, err := hugoPackages()
   293  	if err != nil {
   294  		return err
   295  	}
   296  	for _, pkg := range pkgs {
   297  		if err := sh.Run(goexe, "test", "-coverprofile="+cover, "-covermode=count", pkg); err != nil {
   298  			return err
   299  		}
   300  		b, err := os.ReadFile(cover)
   301  		if err != nil {
   302  			if os.IsNotExist(err) {
   303  				continue
   304  			}
   305  			return err
   306  		}
   307  		idx := bytes.Index(b, []byte{'\n'})
   308  		b = b[idx+1:]
   309  		if _, err := f.Write(b); err != nil {
   310  			return err
   311  		}
   312  	}
   313  	if err := f.Close(); err != nil {
   314  		return err
   315  	}
   316  	return sh.Run(goexe, "tool", "cover", "-html="+coverAll)
   317  }
   318  
   319  func runCmd(env map[string]string, cmd string, args ...any) error {
   320  	if mg.Verbose() {
   321  		return runWith(env, cmd, args...)
   322  	}
   323  	output, err := sh.OutputWith(env, cmd, argsToStrings(args...)...)
   324  	if err != nil {
   325  		fmt.Fprint(os.Stderr, output)
   326  	}
   327  
   328  	return err
   329  }
   330  
   331  func isGoLatest() bool {
   332  	return strings.Contains(runtime.Version(), "1.14")
   333  }
   334  
   335  func isCI() bool {
   336  	return os.Getenv("CI") != ""
   337  }
   338  
   339  func buildFlags() []string {
   340  	if runtime.GOOS == "windows" {
   341  		return []string{"-buildmode", "exe"}
   342  	}
   343  	return nil
   344  }
   345  
   346  func buildTags() string {
   347  	// To build the extended Hugo SCSS/SASS enabled version, build with
   348  	// HUGO_BUILD_TAGS=extended mage install etc.
   349  	// To build without `hugo deploy` for smaller binary, use HUGO_BUILD_TAGS=nodeploy
   350  	if envtags := os.Getenv("HUGO_BUILD_TAGS"); envtags != "" {
   351  		return envtags
   352  	}
   353  	return "none"
   354  }
   355  
   356  func argsToStrings(v ...any) []string {
   357  	var args []string
   358  	for _, arg := range v {
   359  		switch v := arg.(type) {
   360  		case string:
   361  			if v != "" {
   362  				args = append(args, v)
   363  			}
   364  		case []string:
   365  			if v != nil {
   366  				args = append(args, v...)
   367  			}
   368  		default:
   369  			panic("invalid type")
   370  		}
   371  	}
   372  
   373  	return args
   374  }