build.go 11 KB

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