build.go 16 KB

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