build.go 13 KB

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