build.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. // +build ignore
  2. package main
  3. import (
  4. "bytes"
  5. "crypto/md5"
  6. "crypto/sha256"
  7. "encoding/json"
  8. "flag"
  9. "fmt"
  10. "io"
  11. "io/ioutil"
  12. "log"
  13. "os"
  14. "os/exec"
  15. "path"
  16. "path/filepath"
  17. "runtime"
  18. "strconv"
  19. "strings"
  20. "time"
  21. )
  22. const (
  23. windows = "windows"
  24. linux = "linux"
  25. )
  26. var (
  27. //versionRe = regexp.MustCompile(`-[0-9]{1,3}-g[0-9a-f]{5,10}`)
  28. goarch string
  29. goos string
  30. gocc string
  31. cgo bool
  32. pkgArch string
  33. version string = "v1"
  34. // deb & rpm does not support semver so have to handle their version a little differently
  35. linuxPackageVersion string = "v1"
  36. linuxPackageIteration string = ""
  37. race bool
  38. phjsToRelease string
  39. workingDir string
  40. includeBuildId bool = true
  41. buildId string = "0"
  42. binaries []string = []string{"grafana-server", "grafana-cli"}
  43. isDev bool = false
  44. enterprise bool = false
  45. skipRpmGen bool = false
  46. skipDebGen bool = false
  47. printGenVersion bool = false
  48. )
  49. func main() {
  50. log.SetOutput(os.Stdout)
  51. log.SetFlags(0)
  52. ensureGoPath()
  53. var buildIdRaw string
  54. flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
  55. flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
  56. flag.StringVar(&gocc, "cc", "", "CC")
  57. flag.BoolVar(&cgo, "cgo-enabled", cgo, "Enable cgo")
  58. flag.StringVar(&pkgArch, "pkg-arch", "", "PKG ARCH")
  59. flag.StringVar(&phjsToRelease, "phjs", "", "PhantomJS binary")
  60. flag.BoolVar(&race, "race", race, "Use race detector")
  61. flag.BoolVar(&includeBuildId, "includeBuildId", includeBuildId, "IncludeBuildId in package name")
  62. flag.BoolVar(&enterprise, "enterprise", enterprise, "Build enterprise version of Grafana")
  63. flag.StringVar(&buildIdRaw, "buildId", "0", "Build ID from CI system")
  64. flag.BoolVar(&isDev, "dev", isDev, "optimal for development, skips certain steps")
  65. flag.BoolVar(&skipRpmGen, "skipRpm", skipRpmGen, "skip rpm package generation (default: false)")
  66. flag.BoolVar(&skipDebGen, "skipDeb", skipDebGen, "skip deb package generation (default: false)")
  67. flag.BoolVar(&printGenVersion, "gen-version", printGenVersion, "generate Grafana version and output (default: false)")
  68. flag.Parse()
  69. buildId = shortenBuildId(buildIdRaw)
  70. readVersionFromPackageJson()
  71. if pkgArch == "" {
  72. pkgArch = goarch
  73. }
  74. if printGenVersion {
  75. printGeneratedVersion()
  76. return
  77. }
  78. log.Printf("Version: %s, Linux Version: %s, Package Iteration: %s\n", version, linuxPackageVersion, linuxPackageIteration)
  79. if flag.NArg() == 0 {
  80. log.Println("Usage: go run build.go build")
  81. return
  82. }
  83. workingDir, _ = os.Getwd()
  84. for _, cmd := range flag.Args() {
  85. switch cmd {
  86. case "setup":
  87. setup()
  88. case "build-srv":
  89. clean()
  90. build("grafana-server", "./pkg/cmd/grafana-server", []string{})
  91. case "build-cli":
  92. clean()
  93. build("grafana-cli", "./pkg/cmd/grafana-cli", []string{})
  94. case "build-server":
  95. clean()
  96. build("grafana-server", "./pkg/cmd/grafana-server", []string{})
  97. case "build":
  98. //clean()
  99. for _, binary := range binaries {
  100. build(binary, "./pkg/cmd/"+binary, []string{})
  101. }
  102. case "build-frontend":
  103. grunt(gruntBuildArg("build")...)
  104. case "test":
  105. test("./pkg/...")
  106. grunt("test")
  107. case "package":
  108. grunt(gruntBuildArg("build")...)
  109. grunt(gruntBuildArg("package")...)
  110. if goos == linux {
  111. createLinuxPackages()
  112. }
  113. case "package-only":
  114. grunt(gruntBuildArg("package")...)
  115. if goos == linux {
  116. createLinuxPackages()
  117. }
  118. case "pkg-archive":
  119. grunt(gruntBuildArg("package")...)
  120. case "pkg-rpm":
  121. grunt(gruntBuildArg("release")...)
  122. createRpmPackages()
  123. case "pkg-deb":
  124. grunt(gruntBuildArg("release")...)
  125. createDebPackages()
  126. case "sha-dist":
  127. shaFilesInDist()
  128. case "latest":
  129. makeLatestDistCopies()
  130. case "clean":
  131. clean()
  132. default:
  133. log.Fatalf("Unknown command %q", cmd)
  134. }
  135. }
  136. }
  137. func makeLatestDistCopies() {
  138. files, err := ioutil.ReadDir("dist")
  139. if err != nil {
  140. log.Fatalf("failed to create latest copies. Cannot read from /dist")
  141. }
  142. latestMapping := map[string]string{
  143. "_amd64.deb": "dist/grafana_latest_amd64.deb",
  144. ".x86_64.rpm": "dist/grafana-latest-1.x86_64.rpm",
  145. ".linux-amd64.tar.gz": "dist/grafana-latest.linux-x64.tar.gz",
  146. ".linux-armv7.tar.gz": "dist/grafana-latest.linux-armv7.tar.gz",
  147. ".linux-armv6.tar.gz": "dist/grafana-latest.linux-armv6.tar.gz",
  148. ".linux-arm64.tar.gz": "dist/grafana-latest.linux-arm64.tar.gz",
  149. }
  150. for _, file := range files {
  151. for extension, fullName := range latestMapping {
  152. if strings.HasSuffix(file.Name(), extension) {
  153. runError("cp", path.Join("dist", file.Name()), fullName)
  154. }
  155. }
  156. }
  157. }
  158. func readVersionFromPackageJson() {
  159. reader, err := os.Open("package.json")
  160. if err != nil {
  161. log.Fatal("Failed to open package.json")
  162. return
  163. }
  164. defer reader.Close()
  165. jsonObj := map[string]interface{}{}
  166. jsonParser := json.NewDecoder(reader)
  167. if err := jsonParser.Decode(&jsonObj); err != nil {
  168. log.Fatal("Failed to decode package.json")
  169. }
  170. version = jsonObj["version"].(string)
  171. linuxPackageVersion = version
  172. linuxPackageIteration = ""
  173. // handle pre version stuff (deb / rpm does not support semver)
  174. parts := strings.Split(version, "-")
  175. if len(parts) > 1 {
  176. linuxPackageVersion = parts[0]
  177. linuxPackageIteration = parts[1]
  178. }
  179. // add timestamp to iteration
  180. if includeBuildId {
  181. if buildId != "0" {
  182. linuxPackageIteration = fmt.Sprintf("%s%s", buildId, linuxPackageIteration)
  183. } else {
  184. linuxPackageIteration = fmt.Sprintf("%d%s", time.Now().Unix(), linuxPackageIteration)
  185. }
  186. }
  187. }
  188. type linuxPackageOptions struct {
  189. packageType string
  190. packageArch string
  191. homeDir string
  192. binPath string
  193. serverBinPath string
  194. cliBinPath string
  195. configDir string
  196. ldapFilePath string
  197. etcDefaultPath string
  198. etcDefaultFilePath string
  199. initdScriptFilePath string
  200. systemdServiceFilePath string
  201. postinstSrc string
  202. initdScriptSrc string
  203. defaultFileSrc string
  204. systemdFileSrc string
  205. depends []string
  206. }
  207. func createDebPackages() {
  208. debPkgArch := pkgArch
  209. if pkgArch == "armv7" || pkgArch == "armv6" {
  210. debPkgArch = "armhf"
  211. }
  212. createPackage(linuxPackageOptions{
  213. packageType: "deb",
  214. packageArch: debPkgArch,
  215. homeDir: "/usr/share/grafana",
  216. binPath: "/usr/sbin",
  217. configDir: "/etc/grafana",
  218. etcDefaultPath: "/etc/default",
  219. etcDefaultFilePath: "/etc/default/grafana-server",
  220. initdScriptFilePath: "/etc/init.d/grafana-server",
  221. systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service",
  222. postinstSrc: "packaging/deb/control/postinst",
  223. initdScriptSrc: "packaging/deb/init.d/grafana-server",
  224. defaultFileSrc: "packaging/deb/default/grafana-server",
  225. systemdFileSrc: "packaging/deb/systemd/grafana-server.service",
  226. depends: []string{"adduser", "libfontconfig"},
  227. })
  228. }
  229. func createRpmPackages() {
  230. rpmPkgArch := pkgArch
  231. switch {
  232. case pkgArch == "armv7":
  233. rpmPkgArch = "armhfp"
  234. case pkgArch == "arm64":
  235. rpmPkgArch = "aarch64"
  236. }
  237. createPackage(linuxPackageOptions{
  238. packageType: "rpm",
  239. packageArch: rpmPkgArch,
  240. homeDir: "/usr/share/grafana",
  241. binPath: "/usr/sbin",
  242. configDir: "/etc/grafana",
  243. etcDefaultPath: "/etc/sysconfig",
  244. etcDefaultFilePath: "/etc/sysconfig/grafana-server",
  245. initdScriptFilePath: "/etc/init.d/grafana-server",
  246. systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service",
  247. postinstSrc: "packaging/rpm/control/postinst",
  248. initdScriptSrc: "packaging/rpm/init.d/grafana-server",
  249. defaultFileSrc: "packaging/rpm/sysconfig/grafana-server",
  250. systemdFileSrc: "packaging/rpm/systemd/grafana-server.service",
  251. depends: []string{"/sbin/service", "fontconfig", "freetype", "urw-fonts"},
  252. })
  253. }
  254. func createLinuxPackages() {
  255. if !skipDebGen {
  256. createDebPackages()
  257. }
  258. if !skipRpmGen {
  259. createRpmPackages()
  260. }
  261. }
  262. func createPackage(options linuxPackageOptions) {
  263. packageRoot, _ := ioutil.TempDir("", "grafana-linux-pack")
  264. // create directories
  265. runPrint("mkdir", "-p", filepath.Join(packageRoot, options.homeDir))
  266. runPrint("mkdir", "-p", filepath.Join(packageRoot, options.configDir))
  267. runPrint("mkdir", "-p", filepath.Join(packageRoot, "/etc/init.d"))
  268. runPrint("mkdir", "-p", filepath.Join(packageRoot, options.etcDefaultPath))
  269. runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/lib/systemd/system"))
  270. runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/sbin"))
  271. // copy binary
  272. for _, binary := range binaries {
  273. runPrint("cp", "-p", filepath.Join(workingDir, "tmp/bin/"+binary), filepath.Join(packageRoot, "/usr/sbin/"+binary))
  274. }
  275. // copy init.d script
  276. runPrint("cp", "-p", options.initdScriptSrc, filepath.Join(packageRoot, options.initdScriptFilePath))
  277. // copy environment var file
  278. runPrint("cp", "-p", options.defaultFileSrc, filepath.Join(packageRoot, options.etcDefaultFilePath))
  279. // copy systemd file
  280. runPrint("cp", "-p", options.systemdFileSrc, filepath.Join(packageRoot, options.systemdServiceFilePath))
  281. // copy release files
  282. runPrint("cp", "-a", filepath.Join(workingDir, "tmp")+"/.", filepath.Join(packageRoot, options.homeDir))
  283. // remove bin path
  284. runPrint("rm", "-rf", filepath.Join(packageRoot, options.homeDir, "bin"))
  285. args := []string{
  286. "-s", "dir",
  287. "--description", "Grafana",
  288. "-C", packageRoot,
  289. "--url", "https://grafana.com",
  290. "--maintainer", "contact@grafana.com",
  291. "--config-files", options.initdScriptFilePath,
  292. "--config-files", options.etcDefaultFilePath,
  293. "--config-files", options.systemdServiceFilePath,
  294. "--after-install", options.postinstSrc,
  295. "--version", linuxPackageVersion,
  296. "-p", "./dist",
  297. }
  298. name := "grafana"
  299. if enterprise {
  300. name += "-enterprise"
  301. args = append(args, "--replaces", "grafana")
  302. }
  303. fmt.Printf("pkgArch is set to '%s', generated arch is '%s'\n", pkgArch, options.packageArch)
  304. if pkgArch == "armv6" {
  305. name += "-rpi"
  306. args = append(args, "--replaces", "grafana")
  307. }
  308. args = append(args, "--name", name)
  309. description := "Grafana"
  310. if enterprise {
  311. description += " Enterprise"
  312. }
  313. args = append(args, "--vendor", description)
  314. if !enterprise {
  315. args = append(args, "--license", "\"Apache 2.0\"")
  316. }
  317. if options.packageType == "rpm" {
  318. args = append(args, "--rpm-posttrans", "packaging/rpm/control/posttrans")
  319. }
  320. if options.packageType == "deb" {
  321. args = append(args, "--deb-no-default-config-files")
  322. }
  323. if options.packageArch != "" {
  324. args = append(args, "-a", options.packageArch)
  325. }
  326. if linuxPackageIteration != "" {
  327. args = append(args, "--iteration", linuxPackageIteration)
  328. }
  329. // add dependenciesj
  330. for _, dep := range options.depends {
  331. args = append(args, "--depends", dep)
  332. }
  333. args = append(args, ".")
  334. fmt.Println("Creating package: ", options.packageType)
  335. runPrint("fpm", append([]string{"-t", options.packageType}, args...)...)
  336. }
  337. func ensureGoPath() {
  338. if os.Getenv("GOPATH") == "" {
  339. cwd, err := os.Getwd()
  340. if err != nil {
  341. log.Fatal(err)
  342. }
  343. gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
  344. log.Println("GOPATH is", gopath)
  345. os.Setenv("GOPATH", gopath)
  346. }
  347. }
  348. func grunt(params ...string) {
  349. if runtime.GOOS == windows {
  350. runPrint(`.\node_modules\.bin\grunt`, params...)
  351. } else {
  352. runPrint("./node_modules/.bin/grunt", params...)
  353. }
  354. }
  355. func genPackageVersion() string {
  356. if includeBuildId {
  357. return fmt.Sprintf("%v-%v", linuxPackageVersion, linuxPackageIteration)
  358. } else {
  359. return version
  360. }
  361. }
  362. func gruntBuildArg(task string) []string {
  363. args := []string{task}
  364. args = append(args, fmt.Sprintf("--pkgVer=%v", genPackageVersion()))
  365. if pkgArch != "" {
  366. args = append(args, fmt.Sprintf("--arch=%v", pkgArch))
  367. }
  368. if phjsToRelease != "" {
  369. args = append(args, fmt.Sprintf("--phjsToRelease=%v", phjsToRelease))
  370. }
  371. if enterprise {
  372. args = append(args, "--enterprise")
  373. }
  374. args = append(args, fmt.Sprintf("--platform=%v", goos))
  375. return args
  376. }
  377. func setup() {
  378. runPrint("go", "get", "-v", "github.com/golang/dep")
  379. runPrint("go", "install", "-v", "./pkg/cmd/grafana-server")
  380. }
  381. func printGeneratedVersion() {
  382. fmt.Print(genPackageVersion())
  383. }
  384. func test(pkg string) {
  385. setBuildEnv()
  386. runPrint("go", "test", "-short", "-timeout", "60s", pkg)
  387. }
  388. func build(binaryName, pkg string, tags []string) {
  389. binary := fmt.Sprintf("./bin/%s-%s/%s", goos, goarch, binaryName)
  390. if isDev {
  391. //don't include os and arch in output path in dev environment
  392. binary = fmt.Sprintf("./bin/%s", binaryName)
  393. }
  394. if goos == windows {
  395. binary += ".exe"
  396. }
  397. if !isDev {
  398. rmr(binary, binary+".md5")
  399. }
  400. args := []string{"build", "-ldflags", ldflags()}
  401. if len(tags) > 0 {
  402. args = append(args, "-tags", strings.Join(tags, ","))
  403. }
  404. if race {
  405. args = append(args, "-race")
  406. }
  407. args = append(args, "-o", binary)
  408. args = append(args, pkg)
  409. if !isDev {
  410. setBuildEnv()
  411. runPrint("go", "version")
  412. fmt.Printf("Targeting %s/%s\n", goos, goarch)
  413. }
  414. runPrint("go", args...)
  415. if !isDev {
  416. // Create an md5 checksum of the binary, to be included in the archive for
  417. // automatic upgrades.
  418. err := md5File(binary)
  419. if err != nil {
  420. log.Fatal(err)
  421. }
  422. }
  423. }
  424. func ldflags() string {
  425. var b bytes.Buffer
  426. b.WriteString("-w")
  427. b.WriteString(fmt.Sprintf(" -X main.version=%s", version))
  428. b.WriteString(fmt.Sprintf(" -X main.commit=%s", getGitSha()))
  429. b.WriteString(fmt.Sprintf(" -X main.buildstamp=%d", buildStamp()))
  430. b.WriteString(fmt.Sprintf(" -X main.buildBranch=%s", getGitBranch()))
  431. return b.String()
  432. }
  433. func rmr(paths ...string) {
  434. for _, path := range paths {
  435. log.Println("rm -r", path)
  436. os.RemoveAll(path)
  437. }
  438. }
  439. func clean() {
  440. if isDev {
  441. return
  442. }
  443. rmr("dist")
  444. rmr("tmp")
  445. rmr(filepath.Join(os.Getenv("GOPATH"), fmt.Sprintf("pkg/%s_%s/github.com/grafana", goos, goarch)))
  446. }
  447. func setBuildEnv() {
  448. os.Setenv("GOOS", goos)
  449. if goos == windows {
  450. // require windows >=7
  451. os.Setenv("CGO_CFLAGS", "-D_WIN32_WINNT=0x0601")
  452. }
  453. if goarch != "amd64" || goos != linux {
  454. // needed for all other archs
  455. cgo = true
  456. }
  457. if strings.HasPrefix(goarch, "armv") {
  458. os.Setenv("GOARCH", "arm")
  459. os.Setenv("GOARM", goarch[4:])
  460. } else {
  461. os.Setenv("GOARCH", goarch)
  462. }
  463. if goarch == "386" {
  464. os.Setenv("GO386", "387")
  465. }
  466. if cgo {
  467. os.Setenv("CGO_ENABLED", "1")
  468. }
  469. if gocc != "" {
  470. os.Setenv("CC", gocc)
  471. }
  472. }
  473. func getGitBranch() string {
  474. v, err := runError("git", "rev-parse", "--abbrev-ref", "HEAD")
  475. if err != nil {
  476. return "master"
  477. }
  478. return string(v)
  479. }
  480. func getGitSha() string {
  481. v, err := runError("git", "rev-parse", "--short", "HEAD")
  482. if err != nil {
  483. return "unknown-dev"
  484. }
  485. return string(v)
  486. }
  487. func buildStamp() int64 {
  488. bs, err := runError("git", "show", "-s", "--format=%ct")
  489. if err != nil {
  490. return time.Now().Unix()
  491. }
  492. s, _ := strconv.ParseInt(string(bs), 10, 64)
  493. return s
  494. }
  495. func runError(cmd string, args ...string) ([]byte, error) {
  496. ecmd := exec.Command(cmd, args...)
  497. bs, err := ecmd.CombinedOutput()
  498. if err != nil {
  499. return nil, err
  500. }
  501. return bytes.TrimSpace(bs), nil
  502. }
  503. func runPrint(cmd string, args ...string) {
  504. log.Println(cmd, strings.Join(args, " "))
  505. ecmd := exec.Command(cmd, args...)
  506. ecmd.Stdout = os.Stdout
  507. ecmd.Stderr = os.Stderr
  508. err := ecmd.Run()
  509. if err != nil {
  510. log.Fatal(err)
  511. }
  512. }
  513. func md5File(file string) error {
  514. fd, err := os.Open(file)
  515. if err != nil {
  516. return err
  517. }
  518. defer fd.Close()
  519. h := md5.New()
  520. _, err = io.Copy(h, fd)
  521. if err != nil {
  522. return err
  523. }
  524. out, err := os.Create(file + ".md5")
  525. if err != nil {
  526. return err
  527. }
  528. _, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
  529. if err != nil {
  530. return err
  531. }
  532. return out.Close()
  533. }
  534. func shaFilesInDist() {
  535. filepath.Walk("./dist", func(path string, f os.FileInfo, err error) error {
  536. if path == "./dist" {
  537. return nil
  538. }
  539. if !strings.Contains(path, ".sha256") {
  540. err := shaFile(path)
  541. if err != nil {
  542. log.Printf("Failed to create sha file. error: %v\n", err)
  543. }
  544. }
  545. return nil
  546. })
  547. }
  548. func shaFile(file string) error {
  549. fd, err := os.Open(file)
  550. if err != nil {
  551. return err
  552. }
  553. defer fd.Close()
  554. h := sha256.New()
  555. _, err = io.Copy(h, fd)
  556. if err != nil {
  557. return err
  558. }
  559. out, err := os.Create(file + ".sha256")
  560. if err != nil {
  561. return err
  562. }
  563. _, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
  564. if err != nil {
  565. return err
  566. }
  567. return out.Close()
  568. }
  569. func shortenBuildId(buildId string) string {
  570. buildId = strings.Replace(buildId, "-", "", -1)
  571. if len(buildId) < 9 {
  572. return buildId
  573. }
  574. return buildId[0:8]
  575. }