build.go 12 KB

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