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