aur.go 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. package aur
  2. import (
  3. "bytes"
  4. "fmt"
  5. "os"
  6. "os/exec"
  7. "sort"
  8. "strings"
  9. "github.com/jguer/yay/pacman"
  10. )
  11. // TarBin describes the default installation point of tar command.
  12. const TarBin string = "/usr/bin/tar"
  13. // BaseURL givers the AUR default address.
  14. const BaseURL string = "https://aur.archlinux.org"
  15. // MakepkgBin describes the default installation point of makepkg command.
  16. const MakepkgBin string = "/usr/bin/makepkg"
  17. // SearchMode is search without numbers.
  18. const SearchMode int = -1
  19. // NoConfirm ignores prompts.
  20. var NoConfirm = false
  21. // SortMode determines top down package or down top package display
  22. var SortMode = DownTop
  23. // Describes Sorting method for numberdisplay
  24. const (
  25. DownTop = iota
  26. TopDown
  27. )
  28. // Result describes an AUR package.
  29. type Result struct {
  30. ID int `json:"ID"`
  31. Name string `json:"Name"`
  32. PackageBaseID int `json:"PackageBaseID"`
  33. PackageBase string `json:"PackageBase"`
  34. Version string `json:"Version"`
  35. Description string `json:"Description"`
  36. URL string `json:"URL"`
  37. NumVotes int `json:"NumVotes"`
  38. Popularity float32 `json:"Popularity"`
  39. OutOfDate int `json:"OutOfDate"`
  40. Maintainer string `json:"Maintainer"`
  41. FirstSubmitted int `json:"FirstSubmitted"`
  42. LastModified int64 `json:"LastModified"`
  43. URLPath string `json:"URLPath"`
  44. Installed bool
  45. Depends []string `json:"Depends"`
  46. MakeDepends []string `json:"MakeDepends"`
  47. OptDepends []string `json:"OptDepends"`
  48. Conflicts []string `json:"Conflicts"`
  49. Provides []string `json:"Provides"`
  50. License []string `json:"License"`
  51. Keywords []string `json:"Keywords"`
  52. }
  53. // Query is a collection of Results
  54. type Query []Result
  55. func (q Query) Len() int {
  56. return len(q)
  57. }
  58. func (q Query) Less(i, j int) bool {
  59. if SortMode == DownTop {
  60. return q[i].NumVotes < q[j].NumVotes
  61. }
  62. return q[i].NumVotes > q[j].NumVotes
  63. }
  64. func (q Query) Swap(i, j int) {
  65. q[i], q[j] = q[j], q[i]
  66. }
  67. // PrintSearch handles printing search results in a given format
  68. func (q Query) PrintSearch(start int) {
  69. for i, res := range q {
  70. var toprint string
  71. if start != SearchMode {
  72. if SortMode == DownTop {
  73. toprint += fmt.Sprintf("%d ", len(q)+start-i-1)
  74. } else {
  75. toprint += fmt.Sprintf("%d ", start+i)
  76. }
  77. }
  78. toprint += fmt.Sprintf("\x1b[1m%s/\x1b[33m%s \x1b[36m%s \x1b[0m(%d) ", "aur", res.Name, res.Version, res.NumVotes)
  79. if res.Installed == true {
  80. toprint += fmt.Sprintf("\x1b[32;40mInstalled\x1b[0m")
  81. }
  82. toprint += "\n" + res.Description
  83. fmt.Println(toprint)
  84. }
  85. }
  86. // Search returns an AUR search
  87. func Search(pkg string, sortS bool) (Query, int, error) {
  88. type returned struct {
  89. Results Query `json:"results"`
  90. ResultCount int `json:"resultcount"`
  91. }
  92. r := returned{}
  93. err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=search&arg="+pkg, &r)
  94. if sortS {
  95. sort.Sort(r.Results)
  96. }
  97. setter := pacman.PFactory(pFSetTrue)
  98. for i, res := range r.Results {
  99. if i == len(r.Results)-1 {
  100. setter(res.Name, &r.Results[i], true)
  101. continue
  102. }
  103. setter(res.Name, &r.Results[i], false)
  104. }
  105. return r.Results, r.ResultCount, err
  106. }
  107. // This is very dirty but it works so good.
  108. func pFSetTrue(res interface{}) {
  109. f, ok := res.(*Result)
  110. if !ok {
  111. fmt.Println("Unable to convert back to Result")
  112. return
  113. }
  114. f.Installed = true
  115. return
  116. }
  117. // Info returns an AUR search with package details
  118. func Info(pkg string) (Query, int, error) {
  119. type returned struct {
  120. Results Query `json:"results"`
  121. ResultCount int `json:"resultcount"`
  122. }
  123. r := returned{}
  124. err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=info&arg[]="+pkg, &r)
  125. return r.Results, r.ResultCount, err
  126. }
  127. // MultiInfo takes a slice of strings and returns a slice with the info of each package
  128. func MultiInfo(pkgS []string) (Query, int, error) {
  129. type returned struct {
  130. Results Query `json:"results"`
  131. ResultCount int `json:"resultcount"`
  132. }
  133. r := returned{}
  134. var pkg string
  135. for _, pkgn := range pkgS {
  136. pkg += "&arg[]=" + pkgn
  137. }
  138. err := getJSON("https://aur.archlinux.org/rpc/?v=5&type=info"+pkg, &r)
  139. return r.Results, r.ResultCount, err
  140. }
  141. // Install sends system commands to make and install a package from pkgName
  142. func Install(pkg string, baseDir string, flags []string) (err error) {
  143. q, n, err := Info(pkg)
  144. if err != nil {
  145. return
  146. }
  147. if n == 0 {
  148. return fmt.Errorf("Package %s does not exist", pkg)
  149. }
  150. q[0].Install(baseDir, flags)
  151. return err
  152. }
  153. // Upgrade tries to update every foreign package installed in the system
  154. func Upgrade(baseDir string, flags []string) error {
  155. fmt.Println("\x1b[1;36;1m::\x1b[0m\x1b[1m Starting AUR upgrade...\x1b[0m")
  156. foreign, n, err := pacman.ForeignPackages()
  157. if err != nil || n == 0 {
  158. return err
  159. }
  160. keys := make([]string, len(foreign))
  161. i := 0
  162. for k := range foreign {
  163. keys[i] = k
  164. i++
  165. }
  166. q, _, err := MultiInfo(keys)
  167. if err != nil {
  168. return err
  169. }
  170. outdated := q[:0]
  171. for _, res := range q {
  172. if _, ok := foreign[res.Name]; ok {
  173. // Leaving this here for now, warn about downgrades later
  174. if res.LastModified > foreign[res.Name].Date {
  175. fmt.Printf("\x1b[1m\x1b[32m==>\x1b[33;1m %s: \x1b[0m%s \x1b[33;1m-> \x1b[0m%s\n",
  176. res.Name, foreign[res.Name].Version, res.Version)
  177. outdated = append(outdated, res)
  178. }
  179. }
  180. }
  181. //If there are no outdated packages, don't prompt
  182. if len(outdated) == 0 {
  183. fmt.Println(" there is nothing to do")
  184. return nil
  185. }
  186. // Install updated packages
  187. if !NoConfirm {
  188. fmt.Println("\x1b[1m\x1b[32m==> Proceed with upgrade\x1b[0m\x1b[1m (Y/n)\x1b[0m")
  189. var response string
  190. fmt.Scanln(&response)
  191. if strings.ContainsAny(response, "n & N") {
  192. return nil
  193. }
  194. }
  195. for _, pkg := range outdated {
  196. pkg.Install(baseDir, flags)
  197. }
  198. return nil
  199. }
  200. // Install handles install from Result
  201. func (a *Result) Install(baseDir string, flags []string) (err error) {
  202. fmt.Printf("\x1b[1m\x1b[32m==> Installing\x1b[33m %s\x1b[0m\n", a.Name)
  203. // No need to use filepath.separators because it won't run on inferior platforms
  204. err = os.MkdirAll(baseDir+"builds", 0755)
  205. if err != nil {
  206. fmt.Println(err)
  207. return
  208. }
  209. tarLocation := baseDir + a.Name + ".tar.gz"
  210. defer os.Remove(baseDir + a.Name + ".tar.gz")
  211. err = downloadFile(tarLocation, BaseURL+a.URLPath)
  212. if err != nil {
  213. return
  214. }
  215. err = exec.Command(TarBin, "-xf", tarLocation, "-C", baseDir).Run()
  216. if err != nil {
  217. return
  218. }
  219. defer os.RemoveAll(baseDir + a.Name)
  220. var response string
  221. var dir bytes.Buffer
  222. dir.WriteString(baseDir)
  223. dir.WriteString(a.Name)
  224. dir.WriteString("/")
  225. if !NoConfirm {
  226. fmt.Println("\x1b[1m\x1b[32m==> Edit PKGBUILD?\x1b[0m\x1b[1m (y/N)\x1b[0m")
  227. fmt.Scanln(&response)
  228. if strings.ContainsAny(response, "y & Y") {
  229. editcmd := exec.Command(Editor, dir.String()+"PKGBUILD")
  230. editcmd.Stdout = os.Stdout
  231. editcmd.Stderr = os.Stderr
  232. editcmd.Stdin = os.Stdin
  233. editcmd.Run()
  234. }
  235. }
  236. aurDeps, repoDeps, err := a.Dependencies()
  237. if err != nil {
  238. return
  239. }
  240. printDependencies(aurDeps, repoDeps)
  241. if !NoConfirm && (len(aurDeps) != 0 || len(repoDeps) != 0) {
  242. fmt.Println("\x1b[1m\x1b[32m==> Continue?\x1b[0m\x1b[1m (Y/n)\x1b[0m")
  243. fmt.Scanln(&response)
  244. if strings.ContainsAny(response, "n & N") {
  245. return fmt.Errorf("user did not like the dependencies")
  246. }
  247. }
  248. aurQ, n, err := MultiInfo(aurDeps)
  249. if n != len(aurDeps) {
  250. missingDeps(aurDeps, aurQ)
  251. if !NoConfirm {
  252. fmt.Println("\x1b[1m\x1b[32m==> Continue?\x1b[0m\x1b[1m (Y/n)\x1b[0m")
  253. fmt.Scanln(&response)
  254. if strings.ContainsAny(response, "n & N") {
  255. return fmt.Errorf("unable to install dependencies")
  256. }
  257. }
  258. }
  259. // Handle AUR dependencies first
  260. for _, dep := range aurQ {
  261. errA := dep.Install(baseDir, []string{"--asdeps", "--noconfirm"})
  262. if errA != nil {
  263. return errA
  264. }
  265. }
  266. // Repo dependencies
  267. if len(repoDeps) != 0 {
  268. errR := pacman.Install(repoDeps, []string{"--asdeps", "--noconfirm"})
  269. if errR != nil {
  270. pacman.CleanRemove(aurDeps)
  271. return errR
  272. }
  273. }
  274. err = os.Chdir(dir.String())
  275. if err != nil {
  276. return
  277. }
  278. var makepkgcmd *exec.Cmd
  279. var args []string
  280. args = append(args, "-sri")
  281. args = append(args, flags...)
  282. makepkgcmd = exec.Command(MakepkgBin, args...)
  283. makepkgcmd.Stdout = os.Stdout
  284. makepkgcmd.Stderr = os.Stderr
  285. makepkgcmd.Stdin = os.Stdin
  286. err = makepkgcmd.Run()
  287. return
  288. }
  289. func printDependencies(aurDeps []string, repoDeps []string) {
  290. if len(repoDeps) != 0 {
  291. fmt.Print("\x1b[1m\x1b[32m==> Repository dependencies: \x1b[0m")
  292. for _, repoD := range repoDeps {
  293. fmt.Print("\x1b[33m", repoD, " \x1b[0m")
  294. }
  295. fmt.Print("\n")
  296. }
  297. if len(repoDeps) != 0 {
  298. fmt.Print("\x1b[1m\x1b[32m==> AUR dependencies: \x1b[0m")
  299. for _, aurD := range aurDeps {
  300. fmt.Print("\x1b[33m", aurD, " \x1b[0m")
  301. }
  302. fmt.Print("\n")
  303. }
  304. }
  305. func missingDeps(aurDeps []string, aurQ Query) {
  306. for _, depName := range aurDeps {
  307. found := false
  308. for _, dep := range aurQ {
  309. if dep.Name == depName {
  310. found = true
  311. break
  312. }
  313. }
  314. if !found {
  315. fmt.Println("\x1b[31mUnable to find", depName, "in AUR\x1b[0m")
  316. }
  317. }
  318. return
  319. }
  320. // Dependencies returns package dependencies not installed belonging to AUR
  321. func (a *Result) Dependencies() (aur []string, repo []string, err error) {
  322. var q Query
  323. if len(a.Depends) == 0 && len(a.MakeDepends) == 0 {
  324. var n int
  325. q, n, err = Info(a.Name)
  326. if n == 0 || err != nil {
  327. err = fmt.Errorf("Unable to search dependencies, %s", err)
  328. return
  329. }
  330. } else {
  331. q = append(q, *a)
  332. }
  333. aur, repo, err = pacman.OutofRepo(append(q[0].MakeDepends, q[0].Depends...))
  334. return
  335. }