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 }