build.go 15 KB

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