build.go 15 KB

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