diff options
author | khoden <khoden@yandex-team.com> | 2024-12-10 20:01:01 +0300 |
---|---|---|
committer | khoden <khoden@yandex-team.com> | 2024-12-10 20:26:38 +0300 |
commit | eb63fbfeb3d457403c1290a9d4ef893b1659226b (patch) | |
tree | f4d11da6400d4be3bf31c85088f9081c1636ca0d /build/plugins/lib/nots/package_manager | |
parent | cec17a3fe1af8f1a622f38bab3aca68a78798a46 (diff) | |
download | ydb-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.py | 25 |
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): """ |