build.go 14 KB

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