build.go 13 KB

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