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 }