third_party.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. // +build ignore
  2. /*
  3. Copyright 2013 Brandon Philips
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. // This program builds a project and is a copy of third_party.go. See
  15. // github.com/philips/third_party.go
  16. //
  17. // $ go run third_party.go
  18. //
  19. // See the README file for more details.
  20. package main
  21. import (
  22. "fmt"
  23. "io"
  24. "io/ioutil"
  25. "log"
  26. "os"
  27. "os/exec"
  28. "path"
  29. "path/filepath"
  30. "strings"
  31. )
  32. const (
  33. DefaultThirdParty = "third_party"
  34. )
  35. // thirdPartyDir creates a string path to the third_party directory based on
  36. // the current working directory.
  37. func thirdPartyDir() string {
  38. root, err := os.Getwd()
  39. if err != nil {
  40. log.Fatalf("Failed to get the current working directory: %v", err)
  41. }
  42. return path.Join(root, DefaultThirdParty)
  43. }
  44. func srcDir() string {
  45. return path.Join(thirdPartyDir(), "src")
  46. }
  47. // binDir creates a string path to the GOBIN directory based on the current
  48. // working directory.
  49. func binDir() string {
  50. root, err := os.Getwd()
  51. if err != nil {
  52. log.Fatalf("Failed to get the current working directory: %v", err)
  53. }
  54. return path.Join(root, "bin")
  55. }
  56. // run execs a command like a shell script piping everything to the parent's
  57. // stderr/stdout and uses the given environment.
  58. func run(name string, arg ...string) *os.ProcessState {
  59. cmd := exec.Command(name, arg...)
  60. stdout, err := cmd.StdoutPipe()
  61. if err != nil {
  62. fmt.Fprintf(os.Stderr, err.Error())
  63. os.Exit(1)
  64. }
  65. stderr, err := cmd.StderrPipe()
  66. if err != nil {
  67. fmt.Fprintf(os.Stderr, err.Error())
  68. os.Exit(1)
  69. }
  70. err = cmd.Start()
  71. if err != nil {
  72. fmt.Fprintf(os.Stderr, err.Error())
  73. os.Exit(1)
  74. }
  75. go io.Copy(os.Stdout, stdout)
  76. go io.Copy(os.Stderr, stderr)
  77. cmd.Wait()
  78. return cmd.ProcessState
  79. }
  80. // setupProject does the initial setup of the third_party src directory
  81. // including setting up the symlink to the cwd from the src directory.
  82. func setupProject(pkg string) {
  83. root, err := os.Getwd()
  84. if err != nil {
  85. log.Fatalf("Failed to get the current working directory: %v", err)
  86. }
  87. src := path.Join(thirdPartyDir(), "src", pkg)
  88. srcdir := path.Dir(src)
  89. os.MkdirAll(srcdir, 0755)
  90. rel, err := filepath.Rel(srcdir, root)
  91. if err != nil {
  92. log.Fatalf("creating relative third party path: %v", err)
  93. }
  94. err = os.Symlink(rel, src)
  95. if err != nil && os.IsExist(err) == false {
  96. log.Fatalf("creating project third party symlink: %v", err)
  97. }
  98. }
  99. func getVc(root string) versionControl {
  100. for _, v := range []string{".git", ".hg"} {
  101. r := path.Join(root, v)
  102. info, err := os.Stat(r)
  103. if err != nil || !info.IsDir() {
  104. continue
  105. }
  106. base := path.Base(r)
  107. switch base {
  108. case ".git":
  109. return vcGit(r)
  110. case ".hg":
  111. return vcHg(r)
  112. }
  113. }
  114. return new(vcNoop)
  115. }
  116. type versionControl interface {
  117. commit() string
  118. update(string) error
  119. }
  120. // Performs noops on all VC operations.
  121. type vcNoop struct{}
  122. func (v *vcNoop) commit() string {
  123. return ""
  124. }
  125. func (v *vcNoop) update(dir string) error {
  126. return nil
  127. }
  128. type vcHg string
  129. // vcHg.commit returns the current HEAD commit hash for a given hg dir.
  130. func (v vcHg) commit() string {
  131. out, err := exec.Command("hg", "id", "-i", "-R", string(v)).Output()
  132. if err != nil {
  133. return ""
  134. }
  135. return string(out)
  136. }
  137. // vcHg.udpate updates the given hg dir to ref.
  138. func (v vcHg) update(ref string) error {
  139. _, err := exec.Command("hg",
  140. "update",
  141. "-r", ref,
  142. "-R", string(v),
  143. "--cwd", path.Dir(string(v)),
  144. ).Output()
  145. if err != nil {
  146. return err
  147. }
  148. return nil
  149. }
  150. type vcGit string
  151. // vcGit.commit returns the current HEAD commit hash for a given git dir.
  152. func (v vcGit) commit() string {
  153. out, err := exec.Command("git", "--git-dir="+string(v), "rev-parse", "HEAD").Output()
  154. if err != nil {
  155. return ""
  156. }
  157. return string(out)
  158. }
  159. // vcHg.udpate updates the given git dir to ref.
  160. func (v vcGit) update(ref string) error {
  161. _, err := exec.Command("git",
  162. "--work-tree="+path.Dir(string(v)),
  163. "--git-dir="+string(v),
  164. "reset", "--hard", ref,
  165. ).Output()
  166. if err != nil {
  167. return err
  168. }
  169. return nil
  170. }
  171. // commit grabs the commit id from hg or git as a string.
  172. func commit(dir string) string {
  173. return getVc(dir).commit()
  174. }
  175. // removeVcs removes a .git or .hg directory from the given root if it exists.
  176. func removeVcs(root string) (bool, string) {
  177. for _, v := range []string{".git", ".hg"} {
  178. r := path.Join(root, v)
  179. info, err := os.Stat(r)
  180. if err != nil {
  181. continue
  182. }
  183. // We didn't find it, next!
  184. if info.IsDir() == false {
  185. continue
  186. }
  187. // We found it, grab the commit and remove the directory
  188. c := commit(root)
  189. err = os.RemoveAll(r)
  190. if err != nil {
  191. log.Fatalf("removeVcs: %v", err)
  192. }
  193. return true, c
  194. }
  195. return false, ""
  196. }
  197. // bump takes care of grabbing a package, getting the package git hash and
  198. // removing all of the version control stuff.
  199. func bump(pkg, version string) {
  200. tpd := thirdPartyDir()
  201. temp, err := ioutil.TempDir(tpd, "bump")
  202. if err != nil {
  203. log.Fatalf("bump: %v", err)
  204. }
  205. defer os.RemoveAll(temp)
  206. os.Setenv("GOPATH", temp)
  207. run("go", "get", "-u", "-d", pkg)
  208. for {
  209. root := path.Join(temp, "src", pkg) // the temp installation root
  210. home := path.Join(tpd, "src", pkg) // where the package will end up
  211. if version != "" {
  212. err := getVc(root).update(version)
  213. if err != nil {
  214. log.Fatalf("bump: %v", err)
  215. }
  216. }
  217. ok, c := removeVcs(root)
  218. if ok {
  219. // Create the path leading up to the package
  220. err := os.MkdirAll(path.Dir(home), 0755)
  221. if err != nil {
  222. log.Fatalf("bump: %v", err)
  223. }
  224. // Remove anything that might have been there
  225. err = os.RemoveAll(home)
  226. if err != nil {
  227. log.Fatalf("bump: %v", err)
  228. }
  229. // Finally move the package
  230. err = os.Rename(root, home)
  231. if err != nil {
  232. log.Fatalf("bump: %v", err)
  233. }
  234. fmt.Printf("%s %s\n", pkg, strings.TrimSpace(c))
  235. break
  236. }
  237. // Pop off and try to find this directory!
  238. pkg = path.Dir(pkg)
  239. if pkg == "." {
  240. return
  241. }
  242. }
  243. }
  244. // validPkg uses go list to decide if the given path is a valid go package.
  245. // This is used by the bumpAll walk to bump all of the existing packages.
  246. func validPkg(pkg string) bool {
  247. env := append(os.Environ(),
  248. )
  249. cmd := exec.Command("go", "list", pkg)
  250. cmd.Env = env
  251. out, err := cmd.Output()
  252. if err != nil {
  253. return false
  254. }
  255. if pkg == strings.TrimSpace(string(out)) {
  256. return true
  257. }
  258. return false
  259. }
  260. // bumpWalk walks the third_party directory and bumps all of the packages that it finds.
  261. func bumpWalk(path string, info os.FileInfo, err error) error {
  262. if err != nil {
  263. return nil
  264. }
  265. // go packages are always directories
  266. if info.IsDir() == false {
  267. return nil
  268. }
  269. parts := strings.Split(path, srcDir()+"/")
  270. if len(parts) == 1 {
  271. return nil
  272. }
  273. pkg := parts[1]
  274. if validPkg(pkg) == false {
  275. return nil
  276. }
  277. bump(pkg, "")
  278. return nil
  279. }
  280. func bumpAll() {
  281. err := filepath.Walk(srcDir(), bumpWalk)
  282. if err != nil {
  283. log.Fatalf(err.Error())
  284. }
  285. }
  286. func main() {
  287. log.SetFlags(0)
  288. // third_party manages GOPATH, no one else
  289. os.Setenv("GOPATH", thirdPartyDir())
  290. os.Setenv("GOBIN", binDir())
  291. if len(os.Args) <= 1 {
  292. log.Fatalf("No command")
  293. }
  294. cmd := os.Args[1]
  295. if cmd == "setup" && len(os.Args) > 2 {
  296. setupProject(os.Args[2])
  297. return
  298. }
  299. if cmd == "bump" && len(os.Args) > 2 {
  300. ref := ""
  301. if len(os.Args) > 3 {
  302. ref = os.Args[3]
  303. }
  304. bump(os.Args[2], ref)
  305. return
  306. }
  307. if cmd == "bump-all" && len(os.Args) > 1 {
  308. bumpAll()
  309. return
  310. }
  311. ps := run("go", os.Args[1:]...)
  312. if ps.Success() == false {
  313. os.Exit(1)
  314. }
  315. }