build.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. //go:build ignore
  2. package main
  3. import (
  4. "cmp"
  5. "errors"
  6. "flag"
  7. "log"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "runtime"
  12. )
  13. // Flags
  14. var (
  15. flagRegenerateDestroy = flag.Bool("d", false, "force regenerate the dependencies (destructive)")
  16. flagRegenerateGently = flag.Bool("g", false, "regenerate the dependencies (non-destructive)")
  17. flagSkipBuild = flag.Bool("s", false, "generate dependencies only (e.g. skip 'go build .')")
  18. // Flags to set GOARCH explicitly for cross-platform builds,
  19. // e.g., in CI to target a different platform than the build matrix
  20. // default. These allows us to run generate without a separate build
  21. // step for building the script binary for the host ARCH and then
  22. // runing the generate script for the target ARCH. Instead, we can
  23. // just run `go run build.go -target=$GOARCH` to generate the
  24. // deps.
  25. flagGOARCH = flag.String("target", "", "sets GOARCH to use when generating dependencies and building")
  26. )
  27. func buildEnv() []string {
  28. return append(os.Environ(), "GOARCH="+cmp.Or(
  29. *flagGOARCH,
  30. os.Getenv("OLLAMA_BUILD_TARGET_ARCH"),
  31. runtime.GOARCH,
  32. ))
  33. }
  34. func main() {
  35. log.SetFlags(0)
  36. flag.Usage = func() {
  37. log.Printf("Usage: go run build.go [flags]")
  38. log.Println()
  39. log.Println("Flags:")
  40. flag.PrintDefaults()
  41. log.Println()
  42. log.Println("This script builds the Ollama server binary and generates the llama.cpp")
  43. log.Println("bindings for the current platform. It assumes that the current working")
  44. log.Println("directory is the root directory of the Ollama project.")
  45. log.Println()
  46. log.Println("If the -d flag is provided, the script will force regeneration of the")
  47. log.Println("dependencies; removing the 'llm/build' directory before starting.")
  48. log.Println()
  49. log.Println("If the -g flag is provided, the script will regenerate the dependencies")
  50. log.Println("without removing the 'llm/build' directory.")
  51. log.Println()
  52. log.Println("If the -s flag is provided, the script will skip building the Ollama binary")
  53. log.Println()
  54. log.Println("If the -target flag is provided, the script will set GOARCH to the value")
  55. log.Println("of the flag. This is useful for cross-platform builds.")
  56. log.Println()
  57. log.Println("The script will check for the required dependencies (cmake, gcc) and")
  58. log.Println("print their version.")
  59. log.Println()
  60. log.Println("The script will also check if it is being run from the root directory of")
  61. log.Println("the Ollama project.")
  62. log.Println()
  63. os.Exit(1)
  64. }
  65. flag.Parse()
  66. log.Printf("=== Building Ollama ===")
  67. defer func() {
  68. log.Printf("=== Done building Ollama ===")
  69. if !*flagSkipBuild {
  70. log.Println()
  71. log.Println("To run the Ollama server, use:")
  72. log.Println()
  73. log.Println(" ./ollama serve")
  74. log.Println()
  75. }
  76. }()
  77. if flag.NArg() > 0 {
  78. flag.Usage()
  79. }
  80. if !inRootDir() {
  81. log.Fatalf("Please run this script from the root directory of the Ollama project.")
  82. }
  83. if err := checkDependencies(); err != nil {
  84. log.Fatalf("Failed dependency check: %v", err)
  85. }
  86. if err := buildLlammaCPP(); err != nil {
  87. log.Fatalf("Failed to build llama.cpp: %v", err)
  88. }
  89. if err := goBuildOllama(); err != nil {
  90. log.Fatalf("Failed to build ollama Go binary: %v", err)
  91. }
  92. }
  93. // checkDependencies does a quick check to see if the required dependencies are
  94. // installed on the system and functioning enough to print their version.
  95. //
  96. // TODO(bmizerany): Check the actual version of the dependencies? Seems a
  97. // little daunting given diff versions might print diff things. This should
  98. // be good enough for now.
  99. func checkDependencies() error {
  100. var err error
  101. check := func(name string, args ...string) {
  102. log.Printf("=== Checking for %s ===", name)
  103. defer log.Printf("=== Done checking for %s ===\n\n", name)
  104. cmd := exec.Command(name, args...)
  105. cmd.Stdout = os.Stdout
  106. cmd.Stderr = os.Stderr
  107. err = errors.Join(err, cmd.Run())
  108. }
  109. check("cmake", "--version")
  110. check("gcc", "--version")
  111. return err
  112. }
  113. func goBuildOllama() error {
  114. log.Println("=== Building Ollama binary ===")
  115. defer log.Printf("=== Done building Ollama binary ===\n\n")
  116. if *flagSkipBuild {
  117. log.Println("Skipping 'go build -o ollama .'")
  118. return nil
  119. }
  120. cmd := exec.Command("go", "build", "-o", "ollama", ".")
  121. cmd.Stdout = os.Stdout
  122. cmd.Stderr = os.Stderr
  123. cmd.Env = buildEnv()
  124. return cmd.Run()
  125. }
  126. // buildLlammaCPP generates the llama.cpp bindings for the current platform.
  127. //
  128. // It assumes that the current working directory is the root directory of the
  129. // Ollama project.
  130. func buildLlammaCPP() error {
  131. log.Println("=== Generating dependencies ===")
  132. defer log.Printf("=== Done generating dependencies ===\n\n")
  133. if *flagRegenerateDestroy {
  134. if err := os.RemoveAll(filepath.Join("llm", "build")); err != nil {
  135. return err
  136. }
  137. }
  138. if isDirectory(filepath.Join("llm", "build")) && !*flagRegenerateGently {
  139. log.Println("llm/build already exists; skipping. Use -d or -g to re-generate.")
  140. return nil
  141. }
  142. scriptDir, err := filepath.Abs(filepath.Join("llm", "generate"))
  143. if err != nil {
  144. return err
  145. }
  146. var cmd *exec.Cmd
  147. switch runtime.GOOS {
  148. case "windows":
  149. script := filepath.Join(scriptDir, "gen_windows.ps1")
  150. cmd = exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-File", script)
  151. case "linux":
  152. script := filepath.Join(scriptDir, "gen_linux.sh")
  153. cmd = exec.Command("bash", script)
  154. case "darwin":
  155. script := filepath.Join(scriptDir, "gen_darwin.sh")
  156. cmd = exec.Command("bash", script)
  157. default:
  158. log.Fatalf("Unsupported OS: %s", runtime.GOOS)
  159. }
  160. cmd.Dir = filepath.Join("llm", "generate")
  161. cmd.Stdout = os.Stdout
  162. cmd.Stderr = os.Stderr
  163. cmd.Env = buildEnv()
  164. log.Printf("Running GOOS=%s GOARCH=%s %s", runtime.GOOS, runtime.GOARCH, cmd.Args)
  165. return cmd.Run()
  166. }
  167. func isDirectory(path string) bool {
  168. info, err := os.Stat(path)
  169. if err != nil {
  170. return false
  171. }
  172. return info.IsDir()
  173. }
  174. // inRootDir returns true if the current working directory is the root
  175. // directory of the Ollama project. It looks for a file named "go.mod".
  176. func inRootDir() bool {
  177. _, err := os.Stat("go.mod")
  178. return err == nil
  179. }