build.go 13 KB

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