github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/app/build/ui.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // TODO(adg): packages at weekly/release 6 // TODO(adg): some means to register new packages 7 8 // +build appengine 9 10 package build 11 12 import ( 13 "bytes" 14 "errors" 15 "fmt" 16 "html/template" 17 "net/http" 18 "regexp" 19 "sort" 20 "strconv" 21 "strings" 22 23 "cache" 24 25 "appengine" 26 "appengine/datastore" 27 ) 28 29 func init() { 30 handleFunc("/", uiHandler) 31 } 32 33 // uiHandler draws the build status page. 34 func uiHandler(w http.ResponseWriter, r *http.Request) { 35 d := dashboardForRequest(r) 36 c := d.Context(appengine.NewContext(r)) 37 now := cache.Now(c) 38 key := "build-ui" 39 40 page, _ := strconv.Atoi(r.FormValue("page")) 41 if page < 0 { 42 page = 0 43 } 44 key += fmt.Sprintf("-page%v", page) 45 46 branch := r.FormValue("branch") 47 if branch != "" { 48 key += "-branch-" + branch 49 } 50 51 repo := r.FormValue("repo") 52 if repo != "" { 53 key += "-repo-" + repo 54 } 55 56 var b []byte 57 if cache.Get(r, now, key, &b) { 58 w.Write(b) 59 return 60 } 61 62 pkg := &Package{} // empty package is the main repository 63 if repo != "" { 64 var err error 65 pkg, err = GetPackage(c, repo) 66 if err != nil { 67 logErr(w, r, err) 68 return 69 } 70 } 71 commits, err := dashCommits(c, pkg, page, branch) 72 if err != nil { 73 logErr(w, r, err) 74 return 75 } 76 builders := commitBuilders(commits) 77 78 var tipState *TagState 79 if pkg.Kind == "" && page == 0 && (branch == "" || branch == "default") { 80 // only show sub-repo state on first page of normal repo view 81 tipState, err = TagStateByName(c, "tip") 82 if err != nil { 83 logErr(w, r, err) 84 return 85 } 86 } 87 88 p := &Pagination{} 89 if len(commits) == commitsPerPage { 90 p.Next = page + 1 91 } 92 if page > 0 { 93 p.Prev = page - 1 94 p.HasPrev = true 95 } 96 data := &uiTemplateData{d, pkg, commits, builders, tipState, p, branch} 97 98 var buf bytes.Buffer 99 if err := uiTemplate.Execute(&buf, data); err != nil { 100 logErr(w, r, err) 101 return 102 } 103 104 cache.Set(r, now, key, buf.Bytes()) 105 106 buf.WriteTo(w) 107 } 108 109 type Pagination struct { 110 Next, Prev int 111 HasPrev bool 112 } 113 114 // dashCommits gets a slice of the latest Commits to the current dashboard. 115 // If page > 0 it paginates by commitsPerPage. 116 func dashCommits(c appengine.Context, pkg *Package, page int, branch string) ([]*Commit, error) { 117 offset := page * commitsPerPage 118 q := datastore.NewQuery("Commit"). 119 Ancestor(pkg.Key(c)). 120 Order("-Num") 121 122 var commits []*Commit 123 if branch == "" { 124 _, err := q.Limit(commitsPerPage).Offset(offset). 125 GetAll(c, &commits) 126 return commits, err 127 } 128 129 // Look for commits on a specific branch. 130 for t, n := q.Run(c), 0; len(commits) < commitsPerPage && n < 1000; { 131 var c Commit 132 _, err := t.Next(&c) 133 if err == datastore.Done { 134 break 135 } 136 if err != nil { 137 return nil, err 138 } 139 if !isBranchCommit(&c, branch) { 140 continue 141 } 142 if n >= offset { 143 commits = append(commits, &c) 144 } 145 n++ 146 } 147 return commits, nil 148 } 149 150 // isBranchCommit reports whether the given commit is on the specified branch. 151 // It does so by examining the commit description, so there will be some bad 152 // matches where the branch commits do not begin with the "[branch]" prefix. 153 func isBranchCommit(c *Commit, b string) bool { 154 d := strings.TrimSpace(c.Desc) 155 if b == "default" { 156 return !strings.HasPrefix(d, "[") 157 } 158 return strings.HasPrefix(d, "["+b+"]") 159 } 160 161 // commitBuilders returns the names of the builders that provided 162 // Results for the provided commits. 163 func commitBuilders(commits []*Commit) []string { 164 builders := make(map[string]bool) 165 for _, commit := range commits { 166 for _, r := range commit.Results() { 167 builders[r.Builder] = true 168 } 169 } 170 k := keys(builders) 171 sort.Sort(builderOrder(k)) 172 return k 173 } 174 175 func keys(m map[string]bool) (s []string) { 176 for k := range m { 177 s = append(s, k) 178 } 179 sort.Strings(s) 180 return 181 } 182 183 // builderOrder implements sort.Interface, sorting builder names 184 // ("darwin-amd64", etc) first by builderPriority and then alphabetically. 185 type builderOrder []string 186 187 func (s builderOrder) Len() int { return len(s) } 188 func (s builderOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 189 func (s builderOrder) Less(i, j int) bool { 190 pi, pj := builderPriority(s[i]), builderPriority(s[j]) 191 if pi == pj { 192 return s[i] < s[j] 193 } 194 return pi < pj 195 } 196 197 func builderPriority(builder string) (p int) { 198 // Put -temp builders at the end, always. 199 if strings.HasSuffix(builder, "-temp") { 200 defer func() { p += 20 }() 201 } 202 // Group race builders together. 203 if isRace(builder) { 204 return 1 205 } 206 // If the OS has a specified priority, use it. 207 if p, ok := osPriority[builderOS(builder)]; ok { 208 return p 209 } 210 // The rest. 211 return 10 212 } 213 214 func isRace(s string) bool { 215 return strings.Contains(s, "-race-") || strings.HasSuffix(s, "-race") 216 } 217 218 func unsupported(builder string) bool { 219 if strings.HasSuffix(builder, "-temp") { 220 return true 221 } 222 return unsupportedOS(builderOS(builder)) 223 } 224 225 func unsupportedOS(os string) bool { 226 if os == "race" { 227 return false 228 } 229 p, ok := osPriority[os] 230 return !ok || p > 0 231 } 232 233 // Priorities for specific operating systems. 234 var osPriority = map[string]int{ 235 "darwin": 0, 236 "freebsd": 0, 237 "linux": 0, 238 "windows": 0, 239 // race == 1 240 "openbsd": 2, 241 "netbsd": 3, 242 "dragonfly": 4, 243 } 244 245 // TagState represents the state of all Packages at a Tag. 246 type TagState struct { 247 Tag *Commit 248 Packages []*PackageState 249 } 250 251 // PackageState represents the state of a Package at a Tag. 252 type PackageState struct { 253 Package *Package 254 Commit *Commit 255 } 256 257 // TagStateByName fetches the results for all Go subrepos at the specified Tag. 258 func TagStateByName(c appengine.Context, name string) (*TagState, error) { 259 tag, err := GetTag(c, name) 260 if err != nil { 261 return nil, err 262 } 263 pkgs, err := Packages(c, "subrepo") 264 if err != nil { 265 return nil, err 266 } 267 var st TagState 268 for _, pkg := range pkgs { 269 com, err := pkg.LastCommit(c) 270 if err != nil { 271 c.Warningf("%v: no Commit found: %v", pkg, err) 272 continue 273 } 274 st.Packages = append(st.Packages, &PackageState{pkg, com}) 275 } 276 st.Tag, err = tag.Commit(c) 277 if err != nil { 278 return nil, err 279 } 280 return &st, nil 281 } 282 283 type uiTemplateData struct { 284 Dashboard *Dashboard 285 Package *Package 286 Commits []*Commit 287 Builders []string 288 TipState *TagState 289 Pagination *Pagination 290 Branch string 291 } 292 293 var uiTemplate = template.Must( 294 template.New("ui.html").Funcs(tmplFuncs).ParseFiles("build/ui.html"), 295 ) 296 297 var tmplFuncs = template.FuncMap{ 298 "buildDashboards": buildDashboards, 299 "builderOS": builderOS, 300 "builderSpans": builderSpans, 301 "builderSubheading": builderSubheading, 302 "builderTitle": builderTitle, 303 "repoURL": repoURL, 304 "shortDesc": shortDesc, 305 "shortHash": shortHash, 306 "shortUser": shortUser, 307 "tail": tail, 308 "unsupported": unsupported, 309 } 310 311 func splitDash(s string) (string, string) { 312 i := strings.Index(s, "-") 313 if i >= 0 { 314 return s[:i], s[i+1:] 315 } 316 return s, "" 317 } 318 319 // builderOS returns the os tag for a builder string 320 func builderOS(s string) string { 321 os, _ := splitDash(s) 322 return os 323 } 324 325 // builderOSOrRace returns the builder OS or, if it is a race builder, "race". 326 func builderOSOrRace(s string) string { 327 if isRace(s) { 328 return "race" 329 } 330 return builderOS(s) 331 } 332 333 // builderArch returns the arch tag for a builder string 334 func builderArch(s string) string { 335 _, arch := splitDash(s) 336 arch, _ = splitDash(arch) // chop third part 337 return arch 338 } 339 340 // builderSubheading returns a short arch tag for a builder string 341 // or, if it is a race builder, the builder OS. 342 func builderSubheading(s string) string { 343 if isRace(s) { 344 return builderOS(s) 345 } 346 arch := builderArch(s) 347 switch arch { 348 case "amd64": 349 return "x64" 350 } 351 return arch 352 } 353 354 // builderArchChar returns the architecture letter for a builder string 355 func builderArchChar(s string) string { 356 arch := builderArch(s) 357 switch arch { 358 case "386": 359 return "8" 360 case "amd64": 361 return "6" 362 case "arm": 363 return "5" 364 } 365 return arch 366 } 367 368 type builderSpan struct { 369 N int 370 OS string 371 Unsupported bool 372 } 373 374 // builderSpans creates a list of tags showing 375 // the builder's operating system names, spanning 376 // the appropriate number of columns. 377 func builderSpans(s []string) []builderSpan { 378 var sp []builderSpan 379 for len(s) > 0 { 380 i := 1 381 os := builderOSOrRace(s[0]) 382 u := unsupportedOS(os) || strings.HasSuffix(s[0], "-temp") 383 for i < len(s) && builderOSOrRace(s[i]) == os { 384 i++ 385 } 386 sp = append(sp, builderSpan{i, os, u}) 387 s = s[i:] 388 } 389 return sp 390 } 391 392 // builderTitle formats "linux-amd64-foo" as "linux amd64 foo". 393 func builderTitle(s string) string { 394 return strings.Replace(s, "-", " ", -1) 395 } 396 397 // buildDashboards returns the known public dashboards. 398 func buildDashboards() []*Dashboard { 399 return dashboards 400 } 401 402 // shortDesc returns the first line of a description. 403 func shortDesc(desc string) string { 404 if i := strings.Index(desc, "\n"); i != -1 { 405 desc = desc[:i] 406 } 407 return limitStringLength(desc, 100) 408 } 409 410 // shortHash returns a short version of a hash. 411 func shortHash(hash string) string { 412 if len(hash) > 12 { 413 hash = hash[:12] 414 } 415 return hash 416 } 417 418 // shortUser returns a shortened version of a user string. 419 func shortUser(user string) string { 420 if i, j := strings.Index(user, "<"), strings.Index(user, ">"); 0 <= i && i < j { 421 user = user[i+1 : j] 422 } 423 if i := strings.Index(user, "@"); i >= 0 { 424 return user[:i] 425 } 426 return user 427 } 428 429 // repoRe matches Google Code repositories and subrepositories (without paths). 430 var repoRe = regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+)(\.[a-z0-9\-]+)?$`) 431 432 // repoURL returns the URL of a change at a Google Code repository or subrepo. 433 func repoURL(dashboard, hash, packagePath string) (string, error) { 434 if packagePath == "" { 435 if dashboard == "Gccgo" { 436 return "https://code.google.com/p/gofrontend/source/detail?r=" + hash, nil 437 } 438 if dashboard == "Mercurial" { 439 return "https://golang.org/change/" + hash, nil 440 } 441 // TODO(adg): use the above once /change/ points to git hashes 442 return "https://go.googlesource.com/go/+/" + hash, nil 443 } 444 445 // TODO(adg): remove this old hg stuff, one day. 446 if dashboard == "Mercurial" { 447 m := repoRe.FindStringSubmatch(packagePath) 448 if m == nil { 449 return "", errors.New("unrecognized package: " + packagePath) 450 } 451 url := "https://code.google.com/p/" + m[1] + "/source/detail?r=" + hash 452 if len(m) > 2 { 453 url += "&repo=" + m[2][1:] 454 } 455 return url, nil 456 } 457 458 repo := strings.TrimPrefix(packagePath, "golang.org/x/") 459 return "https://go.googlesource.com/" + repo + "/+/" + hash, nil 460 } 461 462 // tail returns the trailing n lines of s. 463 func tail(n int, s string) string { 464 lines := strings.Split(s, "\n") 465 if len(lines) < n { 466 return s 467 } 468 return strings.Join(lines[len(lines)-n:], "\n") 469 }