main.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. package main
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "path"
  8. "strings"
  9. )
  10. // bunch of spaget but it does the job for now
  11. // TODO: tidy up and add a proper build system with CI
  12. const buildPath = "./build"
  13. const archivesPath = "./build/archives"
  14. const executableName = "glance"
  15. const ownerAndRepo = "glanceapp/glance"
  16. const moduleName = "github.com/" + ownerAndRepo
  17. type archiveType int
  18. const (
  19. archiveTypeTarGz archiveType = iota
  20. archiveTypeZip
  21. )
  22. type buildInfo struct {
  23. version string
  24. }
  25. type buildTarget struct {
  26. os string
  27. arch string
  28. armV int
  29. extension string
  30. archive archiveType
  31. }
  32. var buildTargets = []buildTarget{
  33. {
  34. os: "windows",
  35. arch: "amd64",
  36. extension: ".exe",
  37. archive: archiveTypeZip,
  38. },
  39. {
  40. os: "windows",
  41. arch: "arm64",
  42. extension: ".exe",
  43. archive: archiveTypeZip,
  44. },
  45. {
  46. os: "linux",
  47. arch: "amd64",
  48. },
  49. {
  50. os: "linux",
  51. arch: "arm64",
  52. },
  53. {
  54. os: "linux",
  55. arch: "arm",
  56. armV: 6,
  57. },
  58. {
  59. os: "linux",
  60. arch: "arm",
  61. armV: 7,
  62. },
  63. {
  64. os: "openbsd",
  65. arch: "amd64",
  66. },
  67. {
  68. os: "openbsd",
  69. arch: "386",
  70. },
  71. }
  72. func hasUncommitedChanges() (bool, error) {
  73. output, err := exec.Command("git", "status", "--porcelain").CombinedOutput()
  74. if err != nil {
  75. return false, err
  76. }
  77. return len(output) > 0, nil
  78. }
  79. func main() {
  80. flags := flag.NewFlagSet("", flag.ExitOnError)
  81. specificTag := flags.String("tag", "", "Which tagged version to build")
  82. err := flags.Parse(os.Args[1:])
  83. if err != nil {
  84. fmt.Println(err)
  85. os.Exit(1)
  86. }
  87. uncommitedChanges, err := hasUncommitedChanges()
  88. if err != nil {
  89. fmt.Println(err)
  90. os.Exit(1)
  91. }
  92. if uncommitedChanges {
  93. fmt.Println("There are uncommited changes - commit, stash or discard them first")
  94. os.Exit(1)
  95. }
  96. cwd, err := os.Getwd()
  97. if err != nil {
  98. fmt.Println(err)
  99. os.Exit(1)
  100. }
  101. _, err = os.Stat(buildPath)
  102. if err == nil {
  103. fmt.Println("Cleaning up build path")
  104. os.RemoveAll(buildPath)
  105. }
  106. os.Mkdir(buildPath, 0755)
  107. os.Mkdir(archivesPath, 0755)
  108. var version string
  109. if *specificTag == "" {
  110. version, err := getVersionFromGit()
  111. if err != nil {
  112. fmt.Println(version, err)
  113. os.Exit(1)
  114. }
  115. } else {
  116. version = *specificTag
  117. }
  118. output, err := exec.Command("git", "checkout", "tags/"+version).CombinedOutput()
  119. if err != nil {
  120. fmt.Println(string(output))
  121. fmt.Println(err)
  122. os.Exit(1)
  123. }
  124. info := buildInfo{
  125. version: version,
  126. }
  127. for _, target := range buildTargets {
  128. fmt.Printf("Building for %s/%s\n", target.os, target.arch)
  129. if err := build(cwd, info, target); err != nil {
  130. fmt.Println(err)
  131. os.Exit(1)
  132. }
  133. }
  134. versionTag := fmt.Sprintf("%s:%s", ownerAndRepo, version)
  135. latestTag := fmt.Sprintf("%s:latest", ownerAndRepo)
  136. fmt.Println("Building docker image")
  137. var dockerBuildOptions = []string{
  138. "docker", "build",
  139. "--platform=linux/amd64,linux/arm64,linux/arm/v7",
  140. "-t", versionTag,
  141. }
  142. if !strings.Contains(version, "beta") {
  143. dockerBuildOptions = append(dockerBuildOptions, "-t", latestTag)
  144. }
  145. dockerBuildOptions = append(dockerBuildOptions, ".")
  146. output, err = exec.Command("sudo", dockerBuildOptions...).CombinedOutput()
  147. if err != nil {
  148. fmt.Println(string(output))
  149. fmt.Println(err)
  150. os.Exit(1)
  151. }
  152. var input string
  153. fmt.Print("Push docker image? [y/n]: ")
  154. fmt.Scanln(&input)
  155. if input != "y" {
  156. os.Exit(0)
  157. }
  158. output, err = exec.Command(
  159. "sudo", "docker", "push", versionTag,
  160. ).CombinedOutput()
  161. if err != nil {
  162. fmt.Printf("Failed pushing %s:\n", versionTag)
  163. fmt.Println(string(output))
  164. fmt.Println(err)
  165. os.Exit(1)
  166. }
  167. if strings.Contains(version, "beta") {
  168. return
  169. }
  170. output, err = exec.Command(
  171. "sudo", "docker", "push", latestTag,
  172. ).CombinedOutput()
  173. if err != nil {
  174. fmt.Printf("Failed pushing %s:\n", latestTag)
  175. fmt.Println(string(output))
  176. fmt.Println(err)
  177. os.Exit(1)
  178. }
  179. }
  180. func getVersionFromGit() (string, error) {
  181. output, err := exec.Command("git", "describe", "--tags", "--abbrev=0").CombinedOutput()
  182. if err == nil {
  183. return strings.TrimSpace(string(output)), err
  184. }
  185. return string(output), err
  186. }
  187. func archiveFile(name string, target string, t archiveType) error {
  188. var output []byte
  189. var err error
  190. if t == archiveTypeZip {
  191. output, err = exec.Command("zip", "-j", path.Join(archivesPath, name+".zip"), target).CombinedOutput()
  192. } else if t == archiveTypeTarGz {
  193. output, err = exec.Command("tar", "-C", buildPath, "-czf", path.Join(archivesPath, name+".tar.gz"), name).CombinedOutput()
  194. }
  195. if err != nil {
  196. fmt.Println(string(output))
  197. return err
  198. }
  199. return nil
  200. }
  201. func build(workingDir string, info buildInfo, target buildTarget) error {
  202. var name string
  203. if target.arch != "arm" {
  204. name = fmt.Sprintf("%s-%s-%s%s", executableName, target.os, target.arch, target.extension)
  205. } else {
  206. name = fmt.Sprintf("%s-%s-%sv%d", executableName, target.os, target.arch, target.armV)
  207. }
  208. binaryPath := path.Join(buildPath, name)
  209. glancePackage := moduleName + "/internal/glance"
  210. flags := "-s -w"
  211. flags += fmt.Sprintf(" -X %s.buildVersion=%s", glancePackage, info.version)
  212. cmd := exec.Command(
  213. "go",
  214. "build",
  215. "--trimpath",
  216. "--ldflags",
  217. flags,
  218. "-o",
  219. binaryPath,
  220. )
  221. cmd.Dir = workingDir
  222. env := append(os.Environ(), "GOOS="+target.os, "GOARCH="+target.arch, "CGO_ENABLED=0")
  223. if target.arch == "arm" {
  224. env = append(env, fmt.Sprintf("GOARM=%d", target.armV))
  225. }
  226. cmd.Env = env
  227. output, err := cmd.CombinedOutput()
  228. if err != nil {
  229. fmt.Println(err)
  230. fmt.Println(string(output))
  231. return err
  232. }
  233. os.Chmod(binaryPath, 0755)
  234. fmt.Println("Creating archive")
  235. if err := archiveFile(name, binaryPath, target.archive); err != nil {
  236. return err
  237. }
  238. return nil
  239. }