Makefile 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. SHELL=/bin/bash
  2. # Makefile for OpenHands project
  3. # Variables
  4. BACKEND_HOST ?= "127.0.0.1"
  5. BACKEND_PORT = 3000
  6. BACKEND_HOST_PORT = "$(BACKEND_HOST):$(BACKEND_PORT)"
  7. FRONTEND_PORT = 3001
  8. DEFAULT_WORKSPACE_DIR = "./workspace"
  9. DEFAULT_MODEL = "gpt-4o"
  10. CONFIG_FILE = config.toml
  11. PRE_COMMIT_CONFIG_PATH = "./dev_config/python/.pre-commit-config.yaml"
  12. PYTHON_VERSION = 3.11
  13. # ANSI color codes
  14. GREEN=$(shell tput -Txterm setaf 2)
  15. YELLOW=$(shell tput -Txterm setaf 3)
  16. RED=$(shell tput -Txterm setaf 1)
  17. BLUE=$(shell tput -Txterm setaf 6)
  18. RESET=$(shell tput -Txterm sgr0)
  19. # Build
  20. build:
  21. @echo "$(GREEN)Building project...$(RESET)"
  22. @$(MAKE) -s check-dependencies
  23. @$(MAKE) -s install-python-dependencies
  24. @$(MAKE) -s install-frontend-dependencies
  25. @$(MAKE) -s install-pre-commit-hooks
  26. @$(MAKE) -s build-frontend
  27. @echo "$(GREEN)Build completed successfully.$(RESET)"
  28. check-dependencies:
  29. @echo "$(YELLOW)Checking dependencies...$(RESET)"
  30. @$(MAKE) -s check-system
  31. @$(MAKE) -s check-python
  32. @$(MAKE) -s check-npm
  33. @$(MAKE) -s check-nodejs
  34. ifeq ($(INSTALL_DOCKER),)
  35. @$(MAKE) -s check-docker
  36. endif
  37. @$(MAKE) -s check-poetry
  38. @echo "$(GREEN)Dependencies checked successfully.$(RESET)"
  39. check-system:
  40. @echo "$(YELLOW)Checking system...$(RESET)"
  41. @if [ "$(shell uname)" = "Darwin" ]; then \
  42. echo "$(BLUE)macOS detected.$(RESET)"; \
  43. elif [ "$(shell uname)" = "Linux" ]; then \
  44. if [ -f "/etc/manjaro-release" ]; then \
  45. echo "$(BLUE)Manjaro Linux detected.$(RESET)"; \
  46. else \
  47. echo "$(BLUE)Linux detected.$(RESET)"; \
  48. fi; \
  49. elif [ "$$(uname -r | grep -i microsoft)" ]; then \
  50. echo "$(BLUE)Windows Subsystem for Linux detected.$(RESET)"; \
  51. else \
  52. echo "$(RED)Unsupported system detected. Please use macOS, Linux, or Windows Subsystem for Linux (WSL).$(RESET)"; \
  53. exit 1; \
  54. fi
  55. check-python:
  56. @echo "$(YELLOW)Checking Python installation...$(RESET)"
  57. @if command -v python$(PYTHON_VERSION) > /dev/null; then \
  58. echo "$(BLUE)$(shell python$(PYTHON_VERSION) --version) is already installed.$(RESET)"; \
  59. else \
  60. echo "$(RED)Python $(PYTHON_VERSION) is not installed. Please install Python $(PYTHON_VERSION) to continue.$(RESET)"; \
  61. exit 1; \
  62. fi
  63. check-npm:
  64. @echo "$(YELLOW)Checking npm installation...$(RESET)"
  65. @if command -v npm > /dev/null; then \
  66. echo "$(BLUE)npm $(shell npm --version) is already installed.$(RESET)"; \
  67. else \
  68. echo "$(RED)npm is not installed. Please install Node.js to continue.$(RESET)"; \
  69. exit 1; \
  70. fi
  71. check-nodejs:
  72. @echo "$(YELLOW)Checking Node.js installation...$(RESET)"
  73. @if command -v node > /dev/null; then \
  74. NODE_VERSION=$(shell node --version | sed -E 's/v//g'); \
  75. IFS='.' read -r -a NODE_VERSION_ARRAY <<< "$$NODE_VERSION"; \
  76. if [ "$${NODE_VERSION_ARRAY[0]}" -gt 18 ] || ([ "$${NODE_VERSION_ARRAY[0]}" -eq 18 ] && [ "$${NODE_VERSION_ARRAY[1]}" -gt 17 ]) || ([ "$${NODE_VERSION_ARRAY[0]}" -eq 18 ] && [ "$${NODE_VERSION_ARRAY[1]}" -eq 17 ] && [ "$${NODE_VERSION_ARRAY[2]}" -ge 1 ]); then \
  77. echo "$(BLUE)Node.js $$NODE_VERSION is already installed.$(RESET)"; \
  78. else \
  79. echo "$(RED)Node.js 18.17.1 or later is required. Please install Node.js 18.17.1 or later to continue.$(RESET)"; \
  80. exit 1; \
  81. fi; \
  82. else \
  83. echo "$(RED)Node.js is not installed. Please install Node.js to continue.$(RESET)"; \
  84. exit 1; \
  85. fi
  86. check-docker:
  87. @echo "$(YELLOW)Checking Docker installation...$(RESET)"
  88. @if command -v docker > /dev/null; then \
  89. echo "$(BLUE)$(shell docker --version) is already installed.$(RESET)"; \
  90. else \
  91. echo "$(RED)Docker is not installed. Please install Docker to continue.$(RESET)"; \
  92. exit 1; \
  93. fi
  94. check-poetry:
  95. @echo "$(YELLOW)Checking Poetry installation...$(RESET)"
  96. @if command -v poetry > /dev/null; then \
  97. POETRY_VERSION=$(shell poetry --version 2>&1 | sed -E 's/Poetry \(version ([0-9]+\.[0-9]+\.[0-9]+)\)/\1/'); \
  98. IFS='.' read -r -a POETRY_VERSION_ARRAY <<< "$$POETRY_VERSION"; \
  99. if [ $${POETRY_VERSION_ARRAY[0]} -ge 1 ] && [ $${POETRY_VERSION_ARRAY[1]} -ge 8 ]; then \
  100. echo "$(BLUE)$(shell poetry --version) is already installed.$(RESET)"; \
  101. else \
  102. echo "$(RED)Poetry 1.8 or later is required. You can install poetry by running the following command, then adding Poetry to your PATH:"; \
  103. echo "$(RED) curl -sSL https://install.python-poetry.org | python$(PYTHON_VERSION) -$(RESET)"; \
  104. echo "$(RED)More detail here: https://python-poetry.org/docs/#installing-with-the-official-installer$(RESET)"; \
  105. exit 1; \
  106. fi; \
  107. else \
  108. echo "$(RED)Poetry is not installed. You can install poetry by running the following command, then adding Poetry to your PATH:"; \
  109. echo "$(RED) curl -sSL https://install.python-poetry.org | python$(PYTHON_VERSION) -$(RESET)"; \
  110. echo "$(RED)More detail here: https://python-poetry.org/docs/#installing-with-the-official-installer$(RESET)"; \
  111. exit 1; \
  112. fi
  113. install-python-dependencies:
  114. @echo "$(GREEN)Installing Python dependencies...$(RESET)"
  115. @if [ -z "${TZ}" ]; then \
  116. echo "Defaulting TZ (timezone) to UTC"; \
  117. export TZ="UTC"; \
  118. fi
  119. poetry env use python$(PYTHON_VERSION)
  120. @if [ "$(shell uname)" = "Darwin" ]; then \
  121. echo "$(BLUE)Installing chroma-hnswlib...$(RESET)"; \
  122. export HNSWLIB_NO_NATIVE=1; \
  123. poetry run pip install chroma-hnswlib; \
  124. fi
  125. @poetry install --without llama-index
  126. @if [ -f "/etc/manjaro-release" ]; then \
  127. echo "$(BLUE)Detected Manjaro Linux. Installing Playwright dependencies...$(RESET)"; \
  128. poetry run pip install playwright; \
  129. poetry run playwright install chromium; \
  130. else \
  131. if [ ! -f cache/playwright_chromium_is_installed.txt ]; then \
  132. echo "Running playwright install --with-deps chromium..."; \
  133. poetry run playwright install --with-deps chromium; \
  134. mkdir -p cache; \
  135. touch cache/playwright_chromium_is_installed.txt; \
  136. else \
  137. echo "Setup already done. Skipping playwright installation."; \
  138. fi \
  139. fi
  140. @echo "$(GREEN)Python dependencies installed successfully.$(RESET)"
  141. install-frontend-dependencies:
  142. @echo "$(YELLOW)Setting up frontend environment...$(RESET)"
  143. @echo "$(YELLOW)Detect Node.js version...$(RESET)"
  144. @cd frontend && node ./scripts/detect-node-version.js
  145. echo "$(BLUE)Installing frontend dependencies with npm...$(RESET)"
  146. @cd frontend && npm install
  147. @echo "$(GREEN)Frontend dependencies installed successfully.$(RESET)"
  148. install-pre-commit-hooks:
  149. @echo "$(YELLOW)Installing pre-commit hooks...$(RESET)"
  150. @git config --unset-all core.hooksPath || true
  151. @poetry run pre-commit install --config $(PRE_COMMIT_CONFIG_PATH)
  152. @echo "$(GREEN)Pre-commit hooks installed successfully.$(RESET)"
  153. lint-backend:
  154. @echo "$(YELLOW)Running linters...$(RESET)"
  155. @poetry run pre-commit run --files openhands/**/* agenthub/**/* evaluation/**/* --show-diff-on-failure --config $(PRE_COMMIT_CONFIG_PATH)
  156. lint-frontend:
  157. @echo "$(YELLOW)Running linters for frontend...$(RESET)"
  158. @cd frontend && npm run lint
  159. lint:
  160. @$(MAKE) -s lint-frontend
  161. @$(MAKE) -s lint-backend
  162. test-frontend:
  163. @echo "$(YELLOW)Running tests for frontend...$(RESET)"
  164. @cd frontend && npm run test
  165. test:
  166. @$(MAKE) -s test-frontend
  167. build-frontend:
  168. @echo "$(YELLOW)Building frontend...$(RESET)"
  169. @cd frontend && npm run build
  170. # Start backend
  171. start-backend:
  172. @echo "$(YELLOW)Starting backend...$(RESET)"
  173. @poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) --reload --reload-exclude "$(shell pwd)/workspace"
  174. # Start frontend
  175. start-frontend:
  176. @echo "$(YELLOW)Starting frontend...$(RESET)"
  177. @cd frontend && VITE_BACKEND_HOST=$(BACKEND_HOST_PORT) VITE_FRONTEND_PORT=$(FRONTEND_PORT) npm run start
  178. # Common setup for running the app (non-callable)
  179. _run_setup:
  180. @if [ "$(OS)" = "Windows_NT" ]; then \
  181. echo "$(RED) Windows is not supported, use WSL instead!$(RESET)"; \
  182. exit 1; \
  183. fi
  184. @mkdir -p logs
  185. @echo "$(YELLOW)Starting backend server...$(RESET)"
  186. @poetry run uvicorn openhands.server.listen:app --host $(BACKEND_HOST) --port $(BACKEND_PORT) &
  187. @echo "$(YELLOW)Waiting for the backend to start...$(RESET)"
  188. @until nc -z localhost $(BACKEND_PORT); do sleep 0.1; done
  189. @echo "$(GREEN)Backend started successfully.$(RESET)"
  190. # Run the app (standard mode)
  191. run:
  192. @echo "$(YELLOW)Running the app...$(RESET)"
  193. @$(MAKE) -s _run_setup
  194. @cd frontend && echo "$(BLUE)Starting frontend with npm...$(RESET)" && npm run start -- --port $(FRONTEND_PORT)
  195. @echo "$(GREEN)Application started successfully.$(RESET)"
  196. # Run the app (in docker)
  197. docker-run: WORKSPACE_BASE ?= $(PWD)/workspace
  198. docker-run:
  199. @if [ -f /.dockerenv ]; then \
  200. echo "Running inside a Docker container. Exiting..."; \
  201. exit 0; \
  202. else \
  203. echo "$(YELLOW)Running the app in Docker $(OPTIONS)...$(RESET)"; \
  204. export WORKSPACE_BASE=${WORKSPACE_BASE}; \
  205. export SANDBOX_USER_ID=$(shell id -u); \
  206. export DATE=$(shell date +%Y%m%d%H%M%S); \
  207. docker compose up $(OPTIONS); \
  208. fi
  209. # Run the app (WSL mode)
  210. run-wsl:
  211. @echo "$(YELLOW)Running the app in WSL mode...$(RESET)"
  212. @$(MAKE) -s _run_setup
  213. @cd frontend && echo "$(BLUE)Starting frontend with npm (WSL mode)...$(RESET)" && npm run dev_wsl -- --port $(FRONTEND_PORT)
  214. @echo "$(GREEN)Application started successfully in WSL mode.$(RESET)"
  215. # Setup config.toml
  216. setup-config:
  217. @echo "$(YELLOW)Setting up config.toml...$(RESET)"
  218. @$(MAKE) setup-config-prompts
  219. @mv $(CONFIG_FILE).tmp $(CONFIG_FILE)
  220. @echo "$(GREEN)Config.toml setup completed.$(RESET)"
  221. setup-config-prompts:
  222. @echo "[core]" > $(CONFIG_FILE).tmp
  223. @read -p "Enter your workspace directory (as absolute path) [default: $(DEFAULT_WORKSPACE_DIR)]: " workspace_dir; \
  224. workspace_dir=$${workspace_dir:-$(DEFAULT_WORKSPACE_DIR)}; \
  225. echo "workspace_base=\"$$workspace_dir\"" >> $(CONFIG_FILE).tmp
  226. @echo "" >> $(CONFIG_FILE).tmp
  227. @echo "[llm]" >> $(CONFIG_FILE).tmp
  228. @read -p "Enter your LLM model name, used for running without UI. Set the model in the UI after you start the app. (see https://docs.litellm.ai/docs/providers for full list) [default: $(DEFAULT_MODEL)]: " llm_model; \
  229. llm_model=$${llm_model:-$(DEFAULT_MODEL)}; \
  230. echo "model=\"$$llm_model\"" >> $(CONFIG_FILE).tmp
  231. @read -p "Enter your LLM api key: " llm_api_key; \
  232. echo "api_key=\"$$llm_api_key\"" >> $(CONFIG_FILE).tmp
  233. @read -p "Enter your LLM base URL [mostly used for local LLMs, leave blank if not needed - example: http://localhost:5001/v1/]: " llm_base_url; \
  234. if [[ ! -z "$$llm_base_url" ]]; then echo "base_url=\"$$llm_base_url\"" >> $(CONFIG_FILE).tmp; fi
  235. @echo "Enter your LLM Embedding Model"; \
  236. echo "Choices are:"; \
  237. echo " - openai"; \
  238. echo " - azureopenai"; \
  239. echo " - Embeddings available only with OllamaEmbedding:"; \
  240. echo " - llama2"; \
  241. echo " - mxbai-embed-large"; \
  242. echo " - nomic-embed-text"; \
  243. echo " - all-minilm"; \
  244. echo " - stable-code"; \
  245. echo " - bge-m3"; \
  246. echo " - bge-large"; \
  247. echo " - paraphrase-multilingual"; \
  248. echo " - snowflake-arctic-embed"; \
  249. echo " - Leave blank to default to 'BAAI/bge-small-en-v1.5' via huggingface"; \
  250. read -p "> " llm_embedding_model; \
  251. echo "embedding_model=\"$$llm_embedding_model\"" >> $(CONFIG_FILE).tmp; \
  252. if [ "$$llm_embedding_model" = "llama2" ] || [ "$$llm_embedding_model" = "mxbai-embed-large" ] || [ "$$llm_embedding_model" = "nomic-embed-text" ] || [ "$$llm_embedding_model" = "all-minilm" ] || [ "$$llm_embedding_model" = "stable-code" ]; then \
  253. read -p "Enter the local model URL for the embedding model (will set llm.embedding_base_url): " llm_embedding_base_url; \
  254. echo "embedding_base_url=\"$$llm_embedding_base_url\"" >> $(CONFIG_FILE).tmp; \
  255. elif [ "$$llm_embedding_model" = "azureopenai" ]; then \
  256. read -p "Enter the Azure endpoint URL (will overwrite llm.base_url): " llm_base_url; \
  257. echo "base_url=\"$$llm_base_url\"" >> $(CONFIG_FILE).tmp; \
  258. read -p "Enter the Azure LLM Embedding Deployment Name: " llm_embedding_deployment_name; \
  259. echo "embedding_deployment_name=\"$$llm_embedding_deployment_name\"" >> $(CONFIG_FILE).tmp; \
  260. read -p "Enter the Azure API Version: " llm_api_version; \
  261. echo "api_version=\"$$llm_api_version\"" >> $(CONFIG_FILE).tmp; \
  262. fi
  263. # Develop in container
  264. docker-dev:
  265. @if [ -f /.dockerenv ]; then \
  266. echo "Running inside a Docker container. Exiting..."; \
  267. exit 0; \
  268. else \
  269. echo "$(YELLOW)Build and run in Docker $(OPTIONS)...$(RESET)"; \
  270. ./containers/dev/dev.sh $(OPTIONS); \
  271. fi
  272. # Clean up all caches
  273. clean:
  274. @echo "$(YELLOW)Cleaning up caches...$(RESET)"
  275. @rm -rf openhands/.cache
  276. @echo "$(GREEN)Caches cleaned up successfully.$(RESET)"
  277. # Help
  278. help:
  279. @echo "$(BLUE)Usage: make [target]$(RESET)"
  280. @echo "Targets:"
  281. @echo " $(GREEN)build$(RESET) - Build project, including environment setup and dependencies."
  282. @echo " $(GREEN)lint$(RESET) - Run linters on the project."
  283. @echo " $(GREEN)setup-config$(RESET) - Setup the configuration for OpenHands by providing LLM API key,"
  284. @echo " LLM Model name, and workspace directory."
  285. @echo " $(GREEN)start-backend$(RESET) - Start the backend server for the OpenHands project."
  286. @echo " $(GREEN)start-frontend$(RESET) - Start the frontend server for the OpenHands project."
  287. @echo " $(GREEN)run$(RESET) - Run the OpenHands application, starting both backend and frontend servers."
  288. @echo " Backend Log file will be stored in the 'logs' directory."
  289. @echo " $(GREEN)docker-dev$(RESET) - Build and run the OpenHands application in Docker."
  290. @echo " $(GREEN)docker-run$(RESET) - Run the OpenHands application, starting both backend and frontend servers in Docker."
  291. @echo " $(GREEN)help$(RESET) - Display this help message, providing information on available targets."
  292. # Phony targets
  293. .PHONY: build check-dependencies check-python check-npm check-docker check-poetry install-python-dependencies install-frontend-dependencies install-pre-commit-hooks lint start-backend start-frontend run run-wsl setup-config setup-config-prompts help
  294. .PHONY: docker-dev docker-run