diff options
author | miripiruni <miripiruni@yandex-team.ru> | 2022-06-27 19:50:44 +0300 |
---|---|---|
committer | miripiruni <miripiruni@yandex-team.ru> | 2022-06-27 19:50:44 +0300 |
commit | af185767aee9f3fdaa37975bec76bbcaf508af89 (patch) | |
tree | abd59a64baec79e60c373011979b723d874c7f53 /build/plugins/lib/nots | |
parent | b1f45896cfd344d7e216f14a0ae66d385430c5b3 (diff) | |
download | ydb-af185767aee9f3fdaa37975bec76bbcaf508af89.tar.gz |
feat: TS_BUNDLE with dependencies
ref:e744df2a7b177057e6bb9b0fc888a8ed0c56ff0c
Diffstat (limited to 'build/plugins/lib/nots')
-rw-r--r-- | build/plugins/lib/nots/typescript/__init__.py | 6 | ||||
-rw-r--r-- | build/plugins/lib/nots/typescript/ts_bundle_wrapper.py | 87 | ||||
-rw-r--r-- | build/plugins/lib/nots/typescript/ts_config.py | 141 | ||||
-rw-r--r-- | build/plugins/lib/nots/typescript/ts_errors.py | 19 | ||||
-rw-r--r-- | build/plugins/lib/nots/typescript/tsc_wrapper.py | 155 |
5 files changed, 254 insertions, 154 deletions
diff --git a/build/plugins/lib/nots/typescript/__init__.py b/build/plugins/lib/nots/typescript/__init__.py index 4684004183..f7cc9d9c83 100644 --- a/build/plugins/lib/nots/typescript/__init__.py +++ b/build/plugins/lib/nots/typescript/__init__.py @@ -1,7 +1,11 @@ -from .tsc_wrapper import TscWrapper, TsConfig, TsValidationError +from .ts_bundle_wrapper import TsBundleWrapper +from .ts_config import TsConfig +from .ts_errors import TsValidationError +from .tsc_wrapper import TscWrapper __all__ = [ "TscWrapper", + "TsBundleWrapper", "TsConfig", "TsValidationError", ] diff --git a/build/plugins/lib/nots/typescript/ts_bundle_wrapper.py b/build/plugins/lib/nots/typescript/ts_bundle_wrapper.py new file mode 100644 index 0000000000..43a02412ff --- /dev/null +++ b/build/plugins/lib/nots/typescript/ts_bundle_wrapper.py @@ -0,0 +1,87 @@ +import os +import shutil +import subprocess +import tarfile + +from ..package_manager import constants +from .ts_config import TsConfig +from .ts_errors import TsCompilationError + + +class TsBundleWrapper(object): + def __init__(self, build_root, build_path, sources_path, nodejs_bin_path, script_path, ts_config_path, webpack_config_path, webpack_resource): + self.build_root = build_root + self.build_path = build_path + self.sources_path = sources_path + self.nodejs_bin_path = nodejs_bin_path + self.script_path = script_path + self.ts_config_curpath = ts_config_path + self.ts_config_binpath = os.path.join(build_path, os.path.basename(ts_config_path)) + self.webpack_config_curpath = webpack_config_path + self.webpack_config_binpath = os.path.join(build_path, os.path.basename(webpack_config_path)) + self.webpack_resource = webpack_resource + + def compile(self): + self._prepare_dependencies() + self._build_configs() + self._exec_webpack() + self._pack_bundle() + + def _prepare_dependencies(self): + self._copy_package_json() + self._unpack_node_modules() + + def _copy_package_json(self): + # TODO: Validate "main" and "files" - they should include files from the output directory. + shutil.copyfile( + os.path.join(self.sources_path, constants.PACKAGE_JSON_FILENAME), + os.path.join(self.build_path, constants.PACKAGE_JSON_FILENAME), + ) + + def _unpack_node_modules(self): + nm_bundle_path = os.path.join(self.build_path, constants.NODE_MODULES_BUNDLE_FILENAME) + if os.path.isfile(nm_bundle_path): + with tarfile.open(nm_bundle_path) as tf: + tf.extractall(os.path.join(self.build_path, "node_modules")) + + def _build_configs(self): + shutil.copyfile( + self.webpack_config_curpath, + self.webpack_config_binpath + ) + + config = TsConfig.load(self.ts_config_curpath) + config.validate() + config.transform_paths( + build_path=self.build_path, + sources_path=self.sources_path, + ) + + config.path = self.ts_config_binpath + config.write() + + def _exec_webpack(self): + custom_envs = { + "WEBPACK_CONFIG": self.webpack_config_binpath, + "CURDIR": self.sources_path, + "BINDIR": self.build_path, + "NODE_MODULES_DIRS": self.webpack_resource + } + + p = subprocess.Popen( + [self.nodejs_bin_path, self.script_path, "--config", self.webpack_config_binpath], + cwd=self.build_path, + stdin=None, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=custom_envs, + ) + stdout, stderr = p.communicate() + + if p.returncode != 0: + raise TsCompilationError(p.returncode, stdout.decode("utf-8"), stderr.decode("utf-8")) + + def _pack_bundle(self): + with tarfile.open(self.build_path + "/bundle.tar", "w") as tf: + tf.add(self.build_path + "/bundle") + tf.close() diff --git a/build/plugins/lib/nots/typescript/ts_config.py b/build/plugins/lib/nots/typescript/ts_config.py new file mode 100644 index 0000000000..b42ab221d0 --- /dev/null +++ b/build/plugins/lib/nots/typescript/ts_config.py @@ -0,0 +1,141 @@ +import os +import json + +from .ts_errors import TsError, TsValidationError + + +class TsConfig(object): + @classmethod + def load(cls, path): + """ + :param path: tsconfig.json path + :type path: str + :rtype: TsConfig + """ + tsconfig = cls(path) + tsconfig.read() + + return tsconfig + + def __init__(self, path): + if not os.path.isabs(path): + raise TypeError("Absolute path required, given: {}".format(path)) + + self.path = path + self.data = {} + + def read(self): + try: + with open(self.path) as f: + self.data = json.load(f) + except Exception as e: + raise TsError("Failed to read tsconfig {}: {}".format(self.path, e)) + + def get_or_create_compiler_options(self): + """ + Returns ref to the "compilerOptions" dict. + :rtype: dict + """ + opts = self.data.get("compilerOptions") + if opts is None: + opts = {} + self.data["compilerOptions"] = opts + + return opts + + def compiler_option(self, name, default=None): + """ + :param name: option key + :type name: str + :param default: default value + :type default: mixed + :rtype: mixed + """ + return self.get_or_create_compiler_options().get(name, default) + + def validate(self): + """ + Checks whether the config is compatible with current toolchain. + """ + opts = self.get_or_create_compiler_options() + errors = [] + root_dir = opts.get("rootDir") + out_dir = opts.get("outDir") + config_dir = os.path.dirname(self.path) + is_mod_subdir = lambda p: not os.path.isabs(p) and os.path.normpath(os.path.join(config_dir, p)).startswith(config_dir) + + if root_dir is None: + errors.append("'rootDir' option is required") + elif not is_mod_subdir(root_dir): + errors.append("'rootDir' should be a subdirectory of the module") + + if out_dir is None: + errors.append("'outDir' option is required") + elif not is_mod_subdir(out_dir): + errors.append("'outDir' should be a subdirectory of the module") + + if opts.get("outFile") is not None: + errors.append("'outFile' option is not supported") + + if opts.get("preserveSymlinks"): + errors.append("'preserveSymlinks' option is not supported due to pnpm limitations") + + if opts.get("rootDirs") is not None: + errors.append("'rootDirs' option is not supported, relative imports should have single root") + + if self.data.get("files") is not None: + errors.append("'files' option is not supported, use 'include'") + + if self.data.get("references") is not None: + errors.append("composite builds are not supported, use peerdirs in ya.make instead of 'references' option") + + if len(errors): + raise TsValidationError(self.path, errors) + + def transform_paths(self, build_path, sources_path): + """ + Updates config with correct abs paths. + All source files/dirs will be mapped to `sources_path`, output files/dirs will be mapped to `build_path`. + :param build_path: module's build root + :type build_path: str + :param sources_path: module's source root + :type sources_path: str + """ + opts = self.get_or_create_compiler_options() + + sources_path_rel = lambda x: os.path.normpath(os.path.join(sources_path, x)) + build_path_rel = lambda x: os.path.normpath(os.path.join(build_path, x)) + + root_dir = opts["rootDir"] + out_dir = opts["outDir"] + + opts["rootDir"] = sources_path_rel(root_dir) + opts["outDir"] = build_path_rel(out_dir) + + if opts.get("typeRoots"): + opts["typeRoots"] = list(map(sources_path_rel, opts["typeRoots"])) + list(map(build_path_rel, opts["typeRoots"])) + + if opts.get("paths") is None: + opts["paths"] = {} + + # See: https://st.yandex-team.ru/FBP-47#62b4750775525b18f08205c7 + opts["paths"]["*"] = ["*", "./@types/*"] + + opts["baseUrl"] = "./node_modules" + + self.data["include"] = list(map(sources_path_rel, self.data.get("include", []))) + self.data["exclude"] = list(map(sources_path_rel, self.data.get("exclude", []))) + + if opts.get("sourceMap"): + opts["sourceRoot"] = os.path.relpath(root_dir, out_dir) + + def write(self, path=None): + """ + :param path: tsconfig path, defaults to original path + :type path: str + """ + if path is None: + path = self.path + + with open(path, "w") as f: + json.dump(self.data, f) diff --git a/build/plugins/lib/nots/typescript/ts_errors.py b/build/plugins/lib/nots/typescript/ts_errors.py new file mode 100644 index 0000000000..61c6c5dc05 --- /dev/null +++ b/build/plugins/lib/nots/typescript/ts_errors.py @@ -0,0 +1,19 @@ +class TsError(RuntimeError): + pass + + +class TsValidationError(TsError): + def __init__(self, path, errors): + self.path = path + self.errors = errors + + super(TsValidationError, self).__init__("Invalid tsconfig {}:\n{}".format(path, "\n".join(errors))) + + +class TsCompilationError(TsError): + def __init__(self, code, stdout, stderr): + self.code = code + self.stdout = stdout + self.stderr = stderr + + super(TsCompilationError, self).__init__("tsc exited with code {}:\n{}\n{}".format(code, stdout, stderr)) diff --git a/build/plugins/lib/nots/typescript/tsc_wrapper.py b/build/plugins/lib/nots/typescript/tsc_wrapper.py index 9fddf6707f..2b54db7c8e 100644 --- a/build/plugins/lib/nots/typescript/tsc_wrapper.py +++ b/build/plugins/lib/nots/typescript/tsc_wrapper.py @@ -1,162 +1,11 @@ import os -import json import shutil import subprocess import tarfile from ..package_manager import constants - - -class TsError(RuntimeError): - pass - - -class TsValidationError(TsError): - def __init__(self, path, errors): - self.path = path - self.errors = errors - - super(TsValidationError, self).__init__("Invalid tsconfig {}:\n{}".format(path, "\n".join(errors))) - - -class TsCompilationError(TsError): - def __init__(self, code, stdout, stderr): - self.code = code - self.stdout = stdout - self.stderr = stderr - - super(TsCompilationError, self).__init__("tsc exited with code {}:\n{}\n{}".format(code, stdout, stderr)) - - -class TsConfig(object): - @classmethod - def load(cls, path): - """ - :param path: tsconfig.json path - :type path: str - :rtype: TsConfig - """ - tsconfig = cls(path) - tsconfig.read() - - return tsconfig - - def __init__(self, path): - if not os.path.isabs(path): - raise TypeError("Absolute path required, given: {}".format(path)) - - self.path = path - self.data = {} - - def read(self): - try: - with open(self.path) as f: - self.data = json.load(f) - except Exception as e: - raise TsError("Failed to read tsconfig {}: {}".format(self.path, e)) - - def get_or_create_compiler_options(self): - """ - Returns ref to the "compilerOptions" dict. - :rtype: dict - """ - opts = self.data.get("compilerOptions") - if opts is None: - opts = {} - self.data["compilerOptions"] = opts - - return opts - - def compiler_option(self, name, default=None): - """ - :param name: option key - :type name: str - :param default: default value - :type default: mixed - :rtype: mixed - """ - return self.get_or_create_compiler_options().get(name, default) - - def validate(self): - """ - Checks whether the config is compatible with current toolchain. - """ - opts = self.get_or_create_compiler_options() - errors = [] - root_dir = opts.get("rootDir") - out_dir = opts.get("outDir") - config_dir = os.path.dirname(self.path) - is_mod_subdir = lambda p: not os.path.isabs(p) and os.path.normpath(os.path.join(config_dir, p)).startswith(config_dir) - - if root_dir is None: - errors.append("'rootDir' option is required") - elif not is_mod_subdir(root_dir): - errors.append("'rootDir' should be a subdirectory of the module") - - if out_dir is None: - errors.append("'outDir' option is required") - elif not is_mod_subdir(out_dir): - errors.append("'outDir' should be a subdirectory of the module") - - if opts.get("outFile") is not None: - errors.append("'outFile' option is not supported") - - if opts.get("preserveSymlinks"): - errors.append("'preserveSymlinks' option is not supported due to pnpm limitations") - - if opts.get("rootDirs") is not None: - errors.append("'rootDirs' option is not supported, relative imports should have single root") - - if self.data.get("files") is not None: - errors.append("'files' option is not supported, use 'include'") - - if self.data.get("references") is not None: - errors.append("composite builds are not supported, use peerdirs in ya.make instead of 'references' option") - - if len(errors): - raise TsValidationError(self.path, errors) - - def transform_paths(self, build_path, sources_path): - """ - Updates config with correct abs paths. - All source files/dirs will be mapped to `sources_path`, output files/dirs will be mapped to `build_path`. - :param build_path: module's build root - :type build_path: str - :param sources_path: module's source root - :type sources_path: str - """ - opts = self.get_or_create_compiler_options() - - sources_path_rel = lambda x: os.path.normpath(os.path.join(sources_path, x)) - build_path_rel = lambda x: os.path.normpath(os.path.join(build_path, x)) - - root_dir = opts["rootDir"] - out_dir = opts["outDir"] - - opts["rootDir"] = sources_path_rel(root_dir) - opts["outDir"] = build_path_rel(out_dir) - - if opts.get("typeRoots"): - opts["typeRoots"] = list(map(sources_path_rel, opts["typeRoots"])) + list(map(build_path_rel, opts["typeRoots"])) - - opts["baseUrl"] = build_path_rel("node_modules") - - self.data["include"] = list(map(sources_path_rel, self.data.get("include", []))) - self.data["exclude"] = list(map(sources_path_rel, self.data.get("exclude", []))) - - if opts.get("sourceMap"): - opts["sourceRoot"] = os.path.relpath(root_dir, out_dir) - - def write(self, path=None): - """ - :param path: tsconfig path, defaults to original path - :type path: str - """ - if path is None: - path = self.path - - with open(path, "w") as f: - json.dump(self.data, f) +from .ts_config import TsConfig +from .ts_errors import TsCompilationError class TscWrapper(object): |