npm_package_manager.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import os
  2. from ..base import BasePackageManager, PackageManagerError
  3. from ..base.constants import NODE_MODULES_WORKSPACE_BUNDLE_FILENAME
  4. from ..base.node_modules_bundler import bundle_node_modules
  5. from ..base.utils import b_rooted, build_nm_bundle_path, build_pj_path, build_tmp_pj_path, s_rooted
  6. from .npm_lockfile import NpmLockfile
  7. from .npm_utils import build_lockfile_path, build_pre_lockfile_path, build_ws_config_path
  8. from .npm_workspace import NpmWorkspace
  9. class NpmPackageManager(BasePackageManager):
  10. @classmethod
  11. def load_lockfile(cls, path):
  12. """
  13. :param path: path to lockfile
  14. :type path: str
  15. :rtype: NpmLockfile
  16. """
  17. return NpmLockfile.load(path)
  18. @classmethod
  19. def load_lockfile_from_dir(cls, dir_path):
  20. """
  21. :param dir_path: path to directory with lockfile
  22. :type dir_path: str
  23. :rtype: NpmLockfile
  24. """
  25. return cls.load_lockfile(build_lockfile_path(dir_path))
  26. def extract_packages_meta_from_lockfiles(self, lf_paths):
  27. """
  28. :type lf_paths: iterable of BaseLockfile
  29. :rtype: iterable of LockfilePackageMeta
  30. """
  31. tarballs = set()
  32. errors = []
  33. for lf_path in lf_paths:
  34. try:
  35. for pkg in self.load_lockfile(lf_path).get_packages_meta():
  36. if pkg.tarball_path not in tarballs:
  37. tarballs.add(pkg.tarball_path)
  38. yield pkg
  39. except Exception as e:
  40. errors.append("{}: {}".format(lf_path, e))
  41. if errors:
  42. raise PackageManagerError("Unable to process some lockfiles:\n{}".format("\n".join(errors)))
  43. def calc_prepare_deps_inouts_and_resources(
  44. self, store_path: str, has_deps: bool
  45. ) -> tuple[list[str], list[str], list[str]]:
  46. ins = [
  47. s_rooted(build_pj_path(self.module_path)),
  48. s_rooted(build_lockfile_path(self.module_path)),
  49. ]
  50. outs = [
  51. b_rooted(build_pre_lockfile_path(self.module_path)),
  52. b_rooted(build_ws_config_path(self.module_path)),
  53. ]
  54. resources = []
  55. if has_deps:
  56. for pkg in self.extract_packages_meta_from_lockfiles([build_lockfile_path(self.sources_path)]):
  57. resources.append(pkg.to_uri())
  58. outs.append(b_rooted(self._tarballs_store_path(pkg, store_path)))
  59. return ins, outs, resources
  60. def calc_node_modules_inouts(self, local_cli: bool, has_deps: bool) -> tuple[list[str], list[str]]:
  61. """
  62. Returns input and output paths for command that creates `node_modules` bundle.
  63. It relies on .PEERDIRSELF=TS_PREPARE_DEPS
  64. Inputs:
  65. - source package.json
  66. Outputs:
  67. - created node_modules bundle
  68. """
  69. ins = [
  70. s_rooted(build_pj_path(self.module_path)),
  71. ]
  72. outs = []
  73. if not local_cli and has_deps:
  74. outs.append(b_rooted(build_nm_bundle_path(self.module_path)))
  75. return ins, outs
  76. def build_workspace(self, tarballs_store: str):
  77. ws = NpmWorkspace(build_ws_config_path(self.build_path))
  78. ws.set_from_package_json(self._build_package_json())
  79. dep_paths = ws.get_paths(ignore_self=True)
  80. self._build_merged_workspace_config(ws, dep_paths)
  81. self._build_pre_lockfile(tarballs_store)
  82. return ws
  83. def _build_merged_workspace_config(self, ws: NpmWorkspace, dep_paths: list[str]):
  84. """
  85. NOTE: This method mutates `ws`.
  86. """
  87. for dep_path in dep_paths:
  88. ws_config_path = build_ws_config_path(dep_path)
  89. if os.path.isfile(ws_config_path):
  90. ws.merge(NpmWorkspace.load(ws_config_path))
  91. ws.write()
  92. def _build_pre_lockfile(self, tarballs_store: str):
  93. lf = self.load_lockfile_from_dir(self.sources_path)
  94. # Change to the output path for correct path calcs on merging.
  95. lf.path = build_pre_lockfile_path(self.build_path)
  96. lf.update_tarball_resolutions(lambda p: self._tarballs_store_path(p, tarballs_store))
  97. lf.write()
  98. def create_node_modules(self, yatool_prebuilder_path=None, local_cli=False, bundle=True):
  99. """
  100. Creates node_modules directory according to the lockfile.
  101. """
  102. ws = self._prepare_workspace()
  103. for dep_path in ws.get_paths():
  104. module_path = dep_path[len(self.build_root) + 1 :]
  105. dep_source_path = os.path.join(self.sources_root, module_path)
  106. dep_pm = NpmPackageManager(
  107. build_root=self.build_root,
  108. build_path=dep_path,
  109. sources_path=dep_source_path,
  110. nodejs_bin_path=self.nodejs_bin_path,
  111. script_path=self.script_path,
  112. module_path=module_path,
  113. sources_root=self.sources_root,
  114. )
  115. dep_pm._prepare_workspace()
  116. dep_pm._install_node_modules()
  117. self._install_node_modules()
  118. if not local_cli and bundle:
  119. bundle_node_modules(
  120. build_root=self.build_root,
  121. node_modules_path=self._nm_path(),
  122. peers=[],
  123. bundle_path=os.path.join(self.build_path, NODE_MODULES_WORKSPACE_BUNDLE_FILENAME),
  124. )
  125. self._post_install_fix_pj()
  126. def _install_node_modules(self):
  127. install_cmd = ["clean-install", "--ignore-scripts", "--audit=false"]
  128. env = os.environ.copy()
  129. env.update({"NPM_CONFIG_CACHE": os.path.join(self.build_path, ".npm-cache")})
  130. self._exec_command(install_cmd, cwd=self.build_path, env=env)
  131. def _prepare_workspace(self):
  132. lf = self.load_lockfile(build_pre_lockfile_path(self.build_path))
  133. lf.update_tarball_resolutions(lambda p: "file:" + os.path.join(self.build_root, p.tarball_url))
  134. lf.write(build_lockfile_path(self.build_path))
  135. ws = NpmWorkspace.load(build_ws_config_path(self.build_path))
  136. pj = self.load_package_json_from_dir(self.build_path)
  137. pj.write(build_tmp_pj_path(self.build_path)) # save orig file to restore later
  138. pj.data["workspaces"] = list(ws.packages)
  139. os.unlink(pj.path) # for peers this file is hardlinked from RO cache
  140. pj.write()
  141. return ws
  142. def _post_install_fix_pj(self):
  143. os.remove(build_pj_path(self.build_path))
  144. os.rename(build_tmp_pj_path(self.build_path), build_pj_path(self.build_path))