build.go 12 KB

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