build.go 13 KB

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