third_party.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  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. // runEnv execs a command like a shell script piping everything to the parent's
  57. // stderr/stdout and uses the given environment.
  58. func runEnv(env []string, name string, arg ...string) *os.ProcessState {
  59. cmd := exec.Command(name, arg...)
  60. cmd.Env = env
  61. stdout, err := cmd.StdoutPipe()
  62. if err != nil {
  63. fmt.Fprintf(os.Stderr, err.Error())
  64. os.Exit(1)
  65. }
  66. stderr, err := cmd.StderrPipe()
  67. if err != nil {
  68. fmt.Fprintf(os.Stderr, err.Error())
  69. os.Exit(1)
  70. }
  71. err = cmd.Start()
  72. if err != nil {
  73. fmt.Fprintf(os.Stderr, err.Error())
  74. os.Exit(1)
  75. }
  76. go io.Copy(os.Stdout, stdout)
  77. go io.Copy(os.Stderr, stderr)
  78. cmd.Wait()
  79. return cmd.ProcessState
  80. }
  81. // run calls runEnv with the GOPATH third_party packages.
  82. func run(name string, arg ...string) *os.ProcessState {
  83. env := append(os.Environ(),
  84. "GOPATH="+thirdPartyDir(),
  85. "GOBIN="+binDir(),
  86. )
  87. return runEnv(env, name, arg...)
  88. }
  89. // setupProject does the initial setup of the third_party src directory
  90. // including setting up the symlink to the cwd from the src directory.
  91. func setupProject(pkg string) {
  92. root, err := os.Getwd()
  93. if err != nil {
  94. log.Fatalf("Failed to get the current working directory: %v", err)
  95. }
  96. src := path.Join(thirdPartyDir(), "src", pkg)
  97. srcdir := path.Dir(src)
  98. os.MkdirAll(srcdir, 0755)
  99. rel, err := filepath.Rel(srcdir, root)
  100. if err != nil {
  101. log.Fatalf("creating relative third party path: %v", err)
  102. }
  103. err = os.Symlink(rel, src)
  104. if err != nil && os.IsExist(err) == false {
  105. log.Fatalf("creating project third party symlink: %v", err)
  106. }
  107. }
  108. func getVc(root string) versionControl {
  109. for _, v := range []string{".git", ".hg"} {
  110. r := path.Join(root, v)
  111. info, err := os.Stat(r)
  112. if err != nil || !info.IsDir() {
  113. continue
  114. }
  115. base := path.Base(r)
  116. switch base {
  117. case ".git":
  118. return vcGit(r)
  119. case ".hg":
  120. return vcHg(r)
  121. }
  122. }
  123. return new(vcNoop)
  124. }
  125. type versionControl interface {
  126. commit() string
  127. update(string) error
  128. }
  129. // Performs noops on all VC operations.
  130. type vcNoop struct{}
  131. func (v *vcNoop) commit() string {
  132. return ""
  133. }
  134. func (v *vcNoop) update(dir string) error {
  135. return nil
  136. }
  137. type vcHg string
  138. // vcHg.commit returns the current HEAD commit hash for a given hg dir.
  139. func (v vcHg) commit() string {
  140. out, err := exec.Command("hg", "id", "-i", "-R", string(v)).Output()
  141. if err != nil {
  142. return ""
  143. }
  144. return string(out)
  145. }
  146. // vcHg.udpate updates the given hg dir to ref.
  147. func (v vcHg) update(ref string) error {
  148. _, err := exec.Command("hg",
  149. "update",
  150. "-r", ref,
  151. "-R", string(v),
  152. "--cwd", path.Dir(string(v)),
  153. ).Output()
  154. if err != nil {
  155. return err
  156. }
  157. return nil
  158. }
  159. type vcGit string
  160. // vcGit.commit returns the current HEAD commit hash for a given git dir.
  161. func (v vcGit) commit() string {
  162. out, err := exec.Command("git", "--git-dir="+string(v), "rev-parse", "HEAD").Output()
  163. if err != nil {
  164. return ""
  165. }
  166. return string(out)
  167. }
  168. // vcHg.udpate updates the given git dir to ref.
  169. func (v vcGit) update(ref string) error {
  170. _, err := exec.Command("git",
  171. "--work-tree="+path.Dir(string(v)),
  172. "--git-dir="+string(v),
  173. "reset", "--hard", ref,
  174. ).Output()
  175. if err != nil {
  176. return err
  177. }
  178. return nil
  179. }
  180. // commit grabs the commit id from hg or git as a string.
  181. func commit(dir string) string {
  182. return getVc(dir).commit()
  183. }
  184. // removeVcs removes a .git or .hg directory from the given root if it exists.
  185. func removeVcs(root string) (bool, string) {
  186. for _, v := range []string{".git", ".hg"} {
  187. r := path.Join(root, v)
  188. info, err := os.Stat(r)
  189. if err != nil {
  190. continue
  191. }
  192. // We didn't find it, next!
  193. if info.IsDir() == false {
  194. continue
  195. }
  196. // We found it, grab the commit and remove the directory
  197. c := commit(root)
  198. err = os.RemoveAll(r)
  199. if err != nil {
  200. log.Fatalf("removeVcs: %v", err)
  201. }
  202. return true, c
  203. }
  204. return false, ""
  205. }
  206. // bump takes care of grabbing a package, getting the package git hash and
  207. // removing all of the version control stuff.
  208. func bump(pkg, version string) {
  209. tpd := thirdPartyDir()
  210. temp, err := ioutil.TempDir(tpd, "bump")
  211. if err != nil {
  212. log.Fatalf("bump: %v", err)
  213. }
  214. defer os.RemoveAll(temp)
  215. env := append(os.Environ(),
  216. "GOPATH="+temp,
  217. )
  218. runEnv(env, "go", "get", "-u", "-d", pkg)
  219. for {
  220. root := path.Join(temp, "src", pkg) // the temp installation root
  221. home := path.Join(tpd, "src", pkg) // where the package will end up
  222. if version != "" {
  223. err := getVc(root).update(version)
  224. if err != nil {
  225. log.Fatalf("bump: %v", err)
  226. }
  227. }
  228. ok, c := removeVcs(root)
  229. if ok {
  230. // Create the path leading up to the package
  231. err := os.MkdirAll(path.Dir(home), 0755)
  232. if err != nil {
  233. log.Fatalf("bump: %v", err)
  234. }
  235. // Remove anything that might have been there
  236. err = os.RemoveAll(home)
  237. if err != nil {
  238. log.Fatalf("bump: %v", err)
  239. }
  240. // Finally move the package
  241. err = os.Rename(root, home)
  242. if err != nil {
  243. log.Fatalf("bump: %v", err)
  244. }
  245. fmt.Printf("%s %s\n", pkg, strings.TrimSpace(c))
  246. break
  247. }
  248. // Pop off and try to find this directory!
  249. pkg = path.Dir(pkg)
  250. if pkg == "." {
  251. return
  252. }
  253. }
  254. }
  255. // validPkg uses go list to decide if the given path is a valid go package.
  256. // This is used by the bumpAll walk to bump all of the existing packages.
  257. func validPkg(pkg string) bool {
  258. env := append(os.Environ(),
  259. "GOPATH="+thirdPartyDir(),
  260. )
  261. cmd := exec.Command("go", "list", pkg)
  262. cmd.Env = env
  263. out, err := cmd.Output()
  264. if err != nil {
  265. return false
  266. }
  267. if pkg == strings.TrimSpace(string(out)) {
  268. return true
  269. }
  270. return false
  271. }
  272. // bumpWalk walks the third_party directory and bumps all of the packages that it finds.
  273. func bumpWalk(path string, info os.FileInfo, err error) error {
  274. if err != nil {
  275. return nil
  276. }
  277. // go packages are always directories
  278. if info.IsDir() == false {
  279. return nil
  280. }
  281. parts := strings.Split(path, srcDir()+"/")
  282. if len(parts) == 1 {
  283. return nil
  284. }
  285. pkg := parts[1]
  286. if validPkg(pkg) == false {
  287. return nil
  288. }
  289. bump(pkg, "")
  290. return nil
  291. }
  292. func bumpAll() {
  293. err := filepath.Walk(srcDir(), bumpWalk)
  294. if err != nil {
  295. log.Fatalf(err.Error())
  296. }
  297. }
  298. func main() {
  299. log.SetFlags(0)
  300. if len(os.Args) <= 1 {
  301. log.Fatalf("No command")
  302. }
  303. cmd := os.Args[1]
  304. if cmd == "setup" && len(os.Args) > 2 {
  305. setupProject(os.Args[2])
  306. return
  307. }
  308. if cmd == "bump" && len(os.Args) > 2 {
  309. ref := ""
  310. if len(os.Args) > 3 {
  311. ref = os.Args[3]
  312. }
  313. bump(os.Args[2], ref)
  314. return
  315. }
  316. if cmd == "bump-all" && len(os.Args) > 1 {
  317. bumpAll()
  318. return
  319. }
  320. ps := run("go", os.Args[1:]...)
  321. if ps.Success() == false {
  322. os.Exit(1)
  323. }
  324. }