build.go 14 KB

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