build.go 15 KB

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