aboutsummaryrefslogtreecommitdiffstats
path: root/build/plugins/lib/nots/package_manager
diff options
context:
space:
mode:
authorkhoden <khoden@yandex-team.com>2024-12-10 20:01:01 +0300
committerkhoden <khoden@yandex-team.com>2024-12-10 20:26:38 +0300
commiteb63fbfeb3d457403c1290a9d4ef893b1659226b (patch)
treef4d11da6400d4be3bf31c85088f9081c1636ca0d /build/plugins/lib/nots/package_manager
parentcec17a3fe1af8f1a622f38bab3aca68a78798a46 (diff)
downloadydb-eb63fbfeb3d457403c1290a9d4ef893b1659226b.tar.gz
nots: дедупликация действий после сборки, пропуск pnpm install на актуальных node_modules
## Суть изменений: 1. Пересмотрены методы логирования при сборке (и после), чтобы не показывались логи действий, который не выполняются (пропускаются). Таким образом логирование секций кода (с последующим стиранием строки лога) теперь осуществляется в методе-обёртке. 2. Команда `nots install` теперь выполняет последовательную сборку пиров (раньше запускал параллельно, что приводило к состоянию гонки); 3. Сборка пиров в команде `nots install` выполняется без рекурсивного обхода пиров для пиров (все пиры и так переданы). Это позволило не "чинить дедупликацию", а в принципе не приводить к "дупликации" – каждый пир проходится один раз. Тут важно определить порядок обхода. 4. Пропуск повторных запусков `pnpm install` без необходимости. Необходимость вычисляется так: - `builder` в локальном режиме кладет в аутпут файлик `pre.pnpm-lockfile.yaml`, хеш которого используется в `nots/cli` - `builder` в локальном режиме рядом с папкой `node_modules` создает файлик `node_modules.json` с хешом `pre.pnpm-lockfile.yaml`, который использовался при сборке этого `node_modules` - `nots/cli` использует сравнивает файлик из аутпута и из `node_modules.json` и если отличаются, то `pnpm install` запускается. Пожалуй, пункт 4 стоит расписать. Кажется, что эти файлики всегда будут совпадать, но я перестраховываюсь: часть пиров может быть закеширована в сборке, но удалена из `~/.nots/nm_store`, например, при запуске с `nots --clean`. Чтобы избежать подобных локальных казусов я и перестраховываюсь. Хеш от `pre.pnpm-lockfile.yaml` предпочтительнее хеша от `pnpm-lockfile.yaml`, т.к. он включает в себя пиры (т.е. это результат смерживания лок-файлов). Также была версия с проверкой, что node_modules создалась в промежутке между проверкой и запуском nots/cli (т.е. в рамках `ya make`), но это не работает при кешировании узлов сборки пиров. Если у вас будут идеи, какие еще проверки можно сделать для принятия решения, запускать ли `pnpm install` – я открыт к предложениям. ## Побочные улучшения: ### nots/cli - Добавлен хелпер `utils.ts:processItems(items, action)` - отказоустойчивый `forEach`; - Для `log-formatters.ts:unlog` вместо прямой записи ESC-последовательностей в stdout используется модуль `readline`; - `log-formatters.ts:unlog` не срабатывает в тестах (пишет заглушку) и при включении отладочного вывода (`DEBUG`/`--verbose`); - Для `DoneHandler` добавлен метод-обёртка `runOnce(action, key, fn)` для более удобного использования, а также запись в лог отладки, если действие пропускается. ## Что не вошло в PR Осталось на будущее: 1. Дедупликация пиров нескольких таргетов. Т.е. сборка пиров при `nots install project1 project2` должна быть общей, а не своё поддерево для каждого. И `ya make` для них нужно запускать один раз. И пост-сборочные действия выполнять единожды в правильном порядке, деже не пытаясь в дупликацию. 2. Подобный пункту 4 механизм, но не для `nots build`, а `nots install` — не запускать `pnpm install`, если недавно ставили (тут нужно определиться с критерием) commit_hash:11f98acb44f759464876f61c5dbf69da7c0d0340
Diffstat (limited to 'build/plugins/lib/nots/package_manager')
-rw-r--r--build/plugins/lib/nots/package_manager/pnpm/package_manager.py25
1 files changed, 24 insertions, 1 deletions
diff --git a/build/plugins/lib/nots/package_manager/pnpm/package_manager.py b/build/plugins/lib/nots/package_manager/pnpm/package_manager.py
index 96aaf26093..61a160f197 100644
--- a/build/plugins/lib/nots/package_manager/pnpm/package_manager.py
+++ b/build/plugins/lib/nots/package_manager/pnpm/package_manager.py
@@ -1,11 +1,18 @@
+import hashlib
+import json
import os
import shutil
+from .constants import PNPM_PRE_LOCKFILE_FILENAME
from .lockfile import PnpmLockfile
from .utils import build_lockfile_path, build_pre_lockfile_path, build_ws_config_path
from .workspace import PnpmWorkspace
from ..base import BasePackageManager, PackageManagerError
-from ..base.constants import NODE_MODULES_WORKSPACE_BUNDLE_FILENAME, PACKAGE_JSON_FILENAME, PNPM_LOCKFILE_FILENAME
+from ..base.constants import (
+ NODE_MODULES_WORKSPACE_BUNDLE_FILENAME,
+ PACKAGE_JSON_FILENAME,
+ PNPM_LOCKFILE_FILENAME,
+)
from ..base.node_modules_bundler import bundle_node_modules
from ..base.package_json import PackageJson
from ..base.timeit import timeit
@@ -52,6 +59,17 @@ class PnpmPackageManager(BasePackageManager):
return os.path.join(home_dir(), ".cache", "pnpm-store")
@timeit
+ def _get_file_hash(self, path: str):
+ sha256 = hashlib.sha256()
+
+ with open(path, "rb") as f:
+ # Read the file in chunks
+ for chunk in iter(lambda: f.read(4096), b""):
+ sha256.update(chunk)
+
+ return sha256.hexdigest()
+
+ @timeit
def _create_local_node_modules(self, nm_store_path: str, store_dir: str, virtual_store_dir: str):
"""
Creates ~/.nots/nm_store/$MODDIR/node_modules folder (with installed packages and .pnpm/virtual-store)
@@ -85,6 +103,11 @@ class PnpmPackageManager(BasePackageManager):
self._run_pnpm_install(store_dir, virtual_store_dir, nm_store_path)
+ # Write node_modules.json to prevent extra `pnpm install` running 1
+ with open(os.path.join(nm_store_path, "node_modules.json"), "w") as f:
+ pre_pnpm_lockfile_hash = self._get_file_hash(build_pre_lockfile_path(self.build_path))
+ json.dump({PNPM_PRE_LOCKFILE_FILENAME: {"hash": pre_pnpm_lockfile_hash}}, f)
+
@timeit
def create_node_modules(self, yatool_prebuilder_path=None, local_cli=False, bundle=True):
"""