build.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  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. version string = "v1"
  26. race bool
  27. workingDir string
  28. serverBinaryName string = "grafana-server"
  29. )
  30. const minGoVersion = 1.3
  31. func main() {
  32. log.SetOutput(os.Stdout)
  33. log.SetFlags(0)
  34. ensureGoPath()
  35. readVersionFromPackageJson()
  36. log.Printf("Version: %s\n", version)
  37. flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
  38. flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
  39. flag.BoolVar(&race, "race", race, "Use race detector")
  40. flag.Parse()
  41. if flag.NArg() == 0 {
  42. log.Println("Usage: go run build.go build")
  43. return
  44. }
  45. workingDir, _ = os.Getwd()
  46. for _, cmd := range flag.Args() {
  47. switch cmd {
  48. case "setup":
  49. setup()
  50. case "build":
  51. pkg := "."
  52. clean()
  53. build(pkg, []string{})
  54. case "test":
  55. test("./pkg/...")
  56. grunt("test")
  57. case "package":
  58. //verifyGitRepoIsClean()
  59. grunt("release", "--pkgVer="+version)
  60. createLinuxPackages()
  61. case "latest":
  62. makeLatestDistCopies()
  63. case "clean":
  64. clean()
  65. default:
  66. log.Fatalf("Unknown command %q", cmd)
  67. }
  68. }
  69. }
  70. func makeLatestDistCopies() {
  71. runError("cp", "dist/grafana_"+version+"_amd64.deb", "dist/grafana_latest_amd64.deb")
  72. runError("cp", "dist/grafana-"+strings.Replace(version, "-", "_", 5)+"-1.x86_64.rpm", "dist/grafana-latest-1.x86_64.rpm")
  73. runError("cp", "dist/grafana-"+version+".x86_64.tar.gz", "dist/grafana-latest.x86_64.tar.gz")
  74. }
  75. func readVersionFromPackageJson() {
  76. reader, err := os.Open("package.json")
  77. if err != nil {
  78. log.Fatal("Failed to open package.json")
  79. return
  80. }
  81. defer reader.Close()
  82. jsonObj := map[string]interface{}{}
  83. jsonParser := json.NewDecoder(reader)
  84. if err := jsonParser.Decode(&jsonObj); err != nil {
  85. log.Fatal("Failed to decode package.json")
  86. }
  87. version = jsonObj["version"].(string)
  88. }
  89. type linuxPackageOptions struct {
  90. packageType string
  91. homeDir string
  92. binPath string
  93. configDir string
  94. configFilePath string
  95. etcDefaultPath string
  96. etcDefaultFilePath string
  97. initdScriptFilePath string
  98. systemdServiceFilePath string
  99. postinstSrc string
  100. initdScriptSrc string
  101. defaultFileSrc string
  102. systemdFileSrc string
  103. depends []string
  104. }
  105. func createLinuxPackages() {
  106. createPackage(linuxPackageOptions{
  107. packageType: "deb",
  108. homeDir: "/usr/share/grafana",
  109. binPath: "/usr/sbin/grafana-server",
  110. configDir: "/etc/grafana",
  111. configFilePath: "/etc/grafana/grafana.ini",
  112. etcDefaultPath: "/etc/default",
  113. etcDefaultFilePath: "/etc/default/grafana-server",
  114. initdScriptFilePath: "/etc/init.d/grafana-server",
  115. systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service",
  116. postinstSrc: "packaging/deb/control/postinst",
  117. initdScriptSrc: "packaging/deb/init.d/grafana-server",
  118. defaultFileSrc: "packaging/deb/default/grafana-server",
  119. systemdFileSrc: "packaging/deb/systemd/grafana-server.service",
  120. depends: []string{"adduser", "libfontconfig"},
  121. })
  122. createPackage(linuxPackageOptions{
  123. packageType: "rpm",
  124. homeDir: "/usr/share/grafana",
  125. binPath: "/usr/sbin/grafana-server",
  126. configDir: "/etc/grafana",
  127. configFilePath: "/etc/grafana/grafana.ini",
  128. etcDefaultPath: "/etc/sysconfig",
  129. etcDefaultFilePath: "/etc/sysconfig/grafana-server",
  130. initdScriptFilePath: "/etc/init.d/grafana-server",
  131. systemdServiceFilePath: "/usr/lib/systemd/system/grafana-server.service",
  132. postinstSrc: "packaging/rpm/control/postinst",
  133. initdScriptSrc: "packaging/rpm/init.d/grafana-server",
  134. defaultFileSrc: "packaging/rpm/sysconfig/grafana-server",
  135. systemdFileSrc: "packaging/rpm/systemd/grafana-server.service",
  136. depends: []string{"initscripts", "fontconfig"},
  137. })
  138. }
  139. func createPackage(options linuxPackageOptions) {
  140. packageRoot, _ := ioutil.TempDir("", "grafana-linux-pack")
  141. // create directories
  142. runPrint("mkdir", "-p", filepath.Join(packageRoot, options.homeDir))
  143. runPrint("mkdir", "-p", filepath.Join(packageRoot, options.configDir))
  144. runPrint("mkdir", "-p", filepath.Join(packageRoot, "/etc/init.d"))
  145. runPrint("mkdir", "-p", filepath.Join(packageRoot, options.etcDefaultPath))
  146. runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/lib/systemd/system"))
  147. runPrint("mkdir", "-p", filepath.Join(packageRoot, "/usr/sbin"))
  148. // copy binary
  149. runPrint("cp", "-p", filepath.Join(workingDir, "tmp/bin/"+serverBinaryName), filepath.Join(packageRoot, options.binPath))
  150. // copy init.d script
  151. runPrint("cp", "-p", options.initdScriptSrc, filepath.Join(packageRoot, options.initdScriptFilePath))
  152. // copy environment var file
  153. runPrint("cp", "-p", options.defaultFileSrc, filepath.Join(packageRoot, options.etcDefaultFilePath))
  154. // copy systemd file
  155. runPrint("cp", "-p", options.systemdFileSrc, filepath.Join(packageRoot, options.systemdServiceFilePath))
  156. // copy release files
  157. runPrint("cp", "-a", filepath.Join(workingDir, "tmp")+"/.", filepath.Join(packageRoot, options.homeDir))
  158. // remove bin path
  159. runPrint("rm", "-rf", filepath.Join(packageRoot, options.homeDir, "bin"))
  160. // copy sample ini file to /etc/opt/grafana
  161. runPrint("cp", "conf/sample.ini", filepath.Join(packageRoot, options.configFilePath))
  162. args := []string{
  163. "-s", "dir",
  164. "--description", "Grafana",
  165. "-C", packageRoot,
  166. "--vendor", "Grafana",
  167. "--url", "http://grafana.org",
  168. "--license", "Apache 2.0",
  169. "--maintainer", "contact@grafana.org",
  170. "--config-files", options.configFilePath,
  171. "--config-files", options.initdScriptFilePath,
  172. "--config-files", options.etcDefaultFilePath,
  173. "--config-files", options.systemdServiceFilePath,
  174. "--after-install", options.postinstSrc,
  175. "--name", "grafana",
  176. "--version", version,
  177. "-p", "./dist",
  178. }
  179. // add dependenciesj
  180. for _, dep := range options.depends {
  181. args = append(args, "--depends", dep)
  182. }
  183. args = append(args, ".")
  184. fmt.Println("Creating package: ", options.packageType)
  185. runPrint("fpm", append([]string{"-t", options.packageType}, args...)...)
  186. }
  187. func verifyGitRepoIsClean() {
  188. rs, err := runError("git", "ls-files", "--modified")
  189. if err != nil {
  190. log.Fatalf("Failed to check if git tree was clean, %v, %v\n", string(rs), err)
  191. return
  192. }
  193. count := len(string(rs))
  194. if count > 0 {
  195. log.Fatalf("Git repository has modified files, aborting")
  196. }
  197. log.Println("Git repository is clean")
  198. }
  199. func ensureGoPath() {
  200. if os.Getenv("GOPATH") == "" {
  201. cwd, err := os.Getwd()
  202. if err != nil {
  203. log.Fatal(err)
  204. }
  205. gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
  206. log.Println("GOPATH is", gopath)
  207. os.Setenv("GOPATH", gopath)
  208. }
  209. }
  210. func ChangeWorkingDir(dir string) {
  211. os.Chdir(dir)
  212. }
  213. func grunt(params ...string) {
  214. runPrint("./node_modules/grunt-cli/bin/grunt", params...)
  215. }
  216. func setup() {
  217. runPrint("go", "get", "-v", "github.com/tools/godep")
  218. runPrint("go", "get", "-v", "github.com/mattn/go-sqlite3")
  219. runPrint("go", "install", "-v", "github.com/mattn/go-sqlite3")
  220. }
  221. func test(pkg string) {
  222. setBuildEnv()
  223. runPrint("go", "test", "-short", "-timeout", "60s", pkg)
  224. }
  225. func build(pkg string, tags []string) {
  226. binary := "./bin/" + serverBinaryName
  227. if goos == "windows" {
  228. binary += ".exe"
  229. }
  230. rmr(binary, binary+".md5")
  231. args := []string{"build", "-ldflags", ldflags()}
  232. if len(tags) > 0 {
  233. args = append(args, "-tags", strings.Join(tags, ","))
  234. }
  235. if race {
  236. args = append(args, "-race")
  237. }
  238. args = append(args, "-o", binary)
  239. args = append(args, pkg)
  240. setBuildEnv()
  241. runPrint("go", args...)
  242. // Create an md5 checksum of the binary, to be included in the archive for
  243. // automatic upgrades.
  244. err := md5File(binary)
  245. if err != nil {
  246. log.Fatal(err)
  247. }
  248. }
  249. func ldflags() string {
  250. var b bytes.Buffer
  251. b.WriteString("-w")
  252. b.WriteString(fmt.Sprintf(" -X main.version '%s'", version))
  253. b.WriteString(fmt.Sprintf(" -X main.commit '%s'", getGitSha()))
  254. b.WriteString(fmt.Sprintf(" -X main.buildstamp %d", buildStamp()))
  255. return b.String()
  256. }
  257. func rmr(paths ...string) {
  258. for _, path := range paths {
  259. log.Println("rm -r", path)
  260. os.RemoveAll(path)
  261. }
  262. }
  263. func clean() {
  264. rmr("bin", "Godeps/_workspace/pkg", "Godeps/_workspace/bin")
  265. rmr("dist")
  266. rmr("tmp")
  267. rmr(filepath.Join(os.Getenv("GOPATH"), fmt.Sprintf("pkg/%s_%s/github.com/grafana", goos, goarch)))
  268. }
  269. func setBuildEnv() {
  270. os.Setenv("GOOS", goos)
  271. if strings.HasPrefix(goarch, "armv") {
  272. os.Setenv("GOARCH", "arm")
  273. os.Setenv("GOARM", goarch[4:])
  274. } else {
  275. os.Setenv("GOARCH", goarch)
  276. }
  277. if goarch == "386" {
  278. os.Setenv("GO386", "387")
  279. }
  280. wd, err := os.Getwd()
  281. if err != nil {
  282. log.Println("Warning: can't determine current dir:", err)
  283. log.Println("Build might not work as expected")
  284. }
  285. os.Setenv("GOPATH", fmt.Sprintf("%s%c%s", filepath.Join(wd, "Godeps", "_workspace"), os.PathListSeparator, os.Getenv("GOPATH")))
  286. log.Println("GOPATH=" + os.Getenv("GOPATH"))
  287. }
  288. func getGitSha() string {
  289. v, err := runError("git", "describe", "--always", "--dirty")
  290. if err != nil {
  291. return "unknown-dev"
  292. }
  293. v = versionRe.ReplaceAllFunc(v, func(s []byte) []byte {
  294. s[0] = '+'
  295. return s
  296. })
  297. return string(v)
  298. }
  299. func buildStamp() int64 {
  300. bs, err := runError("git", "show", "-s", "--format=%ct")
  301. if err != nil {
  302. return time.Now().Unix()
  303. }
  304. s, _ := strconv.ParseInt(string(bs), 10, 64)
  305. return s
  306. }
  307. func buildArch() string {
  308. os := goos
  309. if os == "darwin" {
  310. os = "macosx"
  311. }
  312. return fmt.Sprintf("%s-%s", os, goarch)
  313. }
  314. func run(cmd string, args ...string) []byte {
  315. bs, err := runError(cmd, args...)
  316. if err != nil {
  317. log.Println(cmd, strings.Join(args, " "))
  318. log.Println(string(bs))
  319. log.Fatal(err)
  320. }
  321. return bytes.TrimSpace(bs)
  322. }
  323. func runError(cmd string, args ...string) ([]byte, error) {
  324. ecmd := exec.Command(cmd, args...)
  325. bs, err := ecmd.CombinedOutput()
  326. if err != nil {
  327. return nil, err
  328. }
  329. return bytes.TrimSpace(bs), nil
  330. }
  331. func runPrint(cmd string, args ...string) {
  332. log.Println(cmd, strings.Join(args, " "))
  333. ecmd := exec.Command(cmd, args...)
  334. ecmd.Stdout = os.Stdout
  335. ecmd.Stderr = os.Stderr
  336. err := ecmd.Run()
  337. if err != nil {
  338. log.Fatal(err)
  339. }
  340. }
  341. func md5File(file string) error {
  342. fd, err := os.Open(file)
  343. if err != nil {
  344. return err
  345. }
  346. defer fd.Close()
  347. h := md5.New()
  348. _, err = io.Copy(h, fd)
  349. if err != nil {
  350. return err
  351. }
  352. out, err := os.Create(file + ".md5")
  353. if err != nil {
  354. return err
  355. }
  356. _, err = fmt.Fprintf(out, "%x\n", h.Sum(nil))
  357. if err != nil {
  358. return err
  359. }
  360. return out.Close()
  361. }