"mmdet3d/datasets/vscode:/vscode.git/clone" did not exist on "7b9bb85bd9d4b8c4bff4d9fde76840b9f0f72fc1"
setup_ts.py 10.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""
Script for building TypeScript modules.
This script is called by `setup.py` and common users should avoid using this directly.

It compiles TypeScript source files in `ts` directory,
and copies (or links) JavaScript output as well as dependencies to `nni_node`.

You can set environment `GLOBAL_TOOLCHAIN=1` to use global node and yarn, if you know what you are doing.
"""

from io import BytesIO
import json
import os
from pathlib import Path
liuzhe-lz's avatar
liuzhe-lz committed
18
import platform
19
20
21
22
import shutil
import subprocess
import sys
import tarfile
23
import traceback
24
25
26
from zipfile import ZipFile


liuzhe-lz's avatar
liuzhe-lz committed
27
node_version = 'v16.14.2'
28
29
yarn_version = 'v1.22.10'

30
31
32
33
34
35
36
37
def _get_jupyter_lab_version():
    try:
        import jupyterlab
        return jupyterlab.__version__
    except ImportError:
        return '3.x'

jupyter_lab_major_version = _get_jupyter_lab_version().split('.')[0]
38
39
40
41
42
43
44

def build(release):
    """
    Compile TypeScript modules and copy or symlink to nni_node directory.

    `release` is the version number without leading letter "v".

45
    If `release` is None or empty, this is a development build and uses symlinks on Linux/macOS;
46
    otherwise this is a release build and copies files instead.
47
    On Windows it always copies files because creating symlink requires extra privilege.
48
49
50
    """
    if release or not os.environ.get('GLOBAL_TOOLCHAIN'):
        download_toolchain()
51
    prepare_nni_node()
52
    update_package()
53
    compile_ts(release)
54
    if release or sys.platform == 'win32':
55
56
57
        copy_nni_node(release)
    else:
        symlink_nni_node()
58
    restore_package()
59

liuzhe-lz's avatar
liuzhe-lz committed
60
def clean():
61
62
63
64
    """
    Remove TypeScript-related intermediate files.
    Python intermediate files are not touched here.
    """
65
    shutil.rmtree('nni_node', ignore_errors=True)
liuzhe-lz's avatar
liuzhe-lz committed
66
    shutil.rmtree('toolchain', ignore_errors=True)
67
68
69
70
71
72
73
74

    for file_or_dir in generated_files:
        path = Path(file_or_dir)
        if path.is_symlink() or path.is_file():
            path.unlink()
        elif path.is_dir():
            shutil.rmtree(path)

75
76
77

if sys.platform == 'linux' or sys.platform == 'darwin':
    node_executable = 'node'
liuzhe-lz's avatar
liuzhe-lz committed
78
79
    _arch = 'x64' if platform.machine() == 'x86_64' else platform.machine()
    node_spec = f'node-{node_version}-{sys.platform}-' + _arch
80
    node_download_url = f'https://nodejs.org/dist/{node_version}/{node_spec}.tar.xz'
81
82
83
    node_extractor = lambda data: tarfile.open(fileobj=BytesIO(data), mode='r:xz')
    node_executable_in_tarball = 'bin/node'

84
85
86
87
88
    yarn_executable = 'yarn'
    yarn_download_url = f'https://github.com/yarnpkg/yarn/releases/download/{yarn_version}/yarn-{yarn_version}.tar.gz'

    path_env_seperator = ':'

89
90
91
elif sys.platform == 'win32':
    node_executable = 'node.exe'
    node_spec = f'node-{node_version}-win-x64'
92
    node_download_url = f'https://nodejs.org/dist/{node_version}/{node_spec}.zip'
93
94
95
    node_extractor = lambda data: ZipFile(BytesIO(data))
    node_executable_in_tarball = 'node.exe'

96
97
98
99
100
    yarn_executable = 'yarn.cmd'
    yarn_download_url = f'https://github.com/yarnpkg/yarn/releases/download/{yarn_version}/yarn-{yarn_version}.tar.gz'

    path_env_seperator = ';'

101
102
103
104
105
106
else:
    raise RuntimeError('Unsupported system')


def download_toolchain():
    """
107
    Download and extract node and yarn.
108
    """
109
    if Path('toolchain/node', node_executable_in_tarball).is_file():
110
        return
111

112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
    Path('toolchain').mkdir(exist_ok=True)
    import requests  # place it here so setup.py can install it before importing

    _print(f'Downloading node.js from {node_download_url}')
    resp = requests.get(node_download_url)
    resp.raise_for_status()
    _print('Extracting node.js')
    tarball = node_extractor(resp.content)
    tarball.extractall('toolchain')
    shutil.rmtree('toolchain/node', ignore_errors=True)
    Path('toolchain', node_spec).rename('toolchain/node')

    _print(f'Downloading yarn from {yarn_download_url}')
    resp = requests.get(yarn_download_url)
    resp.raise_for_status()
    _print('Extracting yarn')
    tarball = tarfile.open(fileobj=BytesIO(resp.content), mode='r:gz')
    tarball.extractall('toolchain')
    shutil.rmtree('toolchain/yarn', ignore_errors=True)
    Path(f'toolchain/yarn-{yarn_version}').rename('toolchain/yarn')

133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
def update_package():
    if jupyter_lab_major_version == '2':
        package_json = json.load(open('ts/jupyter_extension/package.json'))
        json.dump(package_json, open('ts/jupyter_extension/.package_default.json', 'w'), indent=2)

        package_json['scripts']['build'] = 'tsc && jupyter labextension link .'
        package_json['dependencies']['@jupyterlab/application'] = '^2.3.0'
        package_json['dependencies']['@jupyterlab/launcher'] = '^2.3.0'

        package_json['jupyterlab']['outputDir'] = 'build'
        json.dump(package_json, open('ts/jupyter_extension/package.json', 'w'), indent=2)
        print(f'updated package.json with {json.dumps(package_json, indent=2)}')

def restore_package():
    if jupyter_lab_major_version == '2':
        package_json = json.load(open('ts/jupyter_extension/.package_default.json'))
        print(f'stored package.json with {json.dumps(package_json, indent=2)}')
        json.dump(package_json, open('ts/jupyter_extension/package.json', 'w'), indent=2)
        os.remove('ts/jupyter_extension/.package_default.json')
152
153
154
155
156
157
158
159
160
161
162
163

def prepare_nni_node():
    """
    Create clean nni_node diretory, then copy node runtime to it.
    """
    shutil.rmtree('nni_node', ignore_errors=True)
    Path('nni_node').mkdir()

    Path('nni_node/__init__.py').write_text('"""NNI node.js modules."""\n')

    node_src = Path('toolchain/node', node_executable_in_tarball)
    node_dst = Path('nni_node', node_executable)
liuzhe-lz's avatar
liuzhe-lz committed
164
    shutil.copy(node_src, node_dst)
165
166


167
def compile_ts(release):
168
169
170
171
172
173
174
175
176
177
178
179
    """
    Use yarn to download dependencies and compile TypeScript code.
    """
    _print('Building NNI manager')
    _yarn('ts/nni_manager')
    _yarn('ts/nni_manager', 'build')
    # todo: I don't think these should be here
    shutil.rmtree('ts/nni_manager/dist/config', ignore_errors=True)
    shutil.copytree('ts/nni_manager/config', 'ts/nni_manager/dist/config')

    _print('Building web UI')
    _yarn('ts/webui')
180
181
182
183
    if release:
        _yarn('ts/webui', 'release')
    else:
        _yarn('ts/webui', 'build')
184

liuzhe-lz's avatar
liuzhe-lz committed
185
    _print('Building JupyterLab extension')
liuzhe-lz's avatar
liuzhe-lz committed
186
    try:
187
188
        _yarn('ts/jupyter_extension')
        _yarn('ts/jupyter_extension', 'build')
liuzhe-lz's avatar
liuzhe-lz committed
189
190
191
192
193
    except Exception:
        if release:
            raise
        _print('Failed to build JupyterLab extension, skip for develop mode', color='yellow')
        _print(traceback.format_exc(), color='yellow')
liuzhe-lz's avatar
liuzhe-lz committed
194

195
196
197
198
199
200
201
202
203
204
205
206
207
208
209

def symlink_nni_node():
    """
    Create symlinks to compiled JS files.
    If you manually modify and compile TS source files you don't need to install again.
    """
    _print('Creating symlinks')

    for path in Path('ts/nni_manager/dist').iterdir():
        _symlink(path, Path('nni_node', path.name))
    _symlink('ts/nni_manager/package.json', 'nni_node/package.json')
    _symlink('ts/nni_manager/node_modules', 'nni_node/node_modules')

    _symlink('ts/webui/build', 'nni_node/static')

210
211
212
213
    if jupyter_lab_major_version == '2':
        _symlink('ts/jupyter_extension/build', 'nni_node/jupyter-extension')
        _symlink(os.path.join(sys.exec_prefix, 'share/jupyter/lab/extensions'), 'nni_node/jupyter-extension/extensions')
    elif Path('ts/jupyter_extension/dist').exists():
214
        _symlink('ts/jupyter_extension/dist', 'nni_node/jupyter-extension')
liuzhe-lz's avatar
liuzhe-lz committed
215

216
217
218
219
220
221
222
223
224
225

def copy_nni_node(version):
    """
    Copy compiled JS files to nni_node.
    This is meant for building release package, so you need to provide version string.
    The version will written to `package.json` in nni_node directory,
    while `package.json` in ts directory will be left unchanged.
    """
    _print('Copying files')

J-shang's avatar
J-shang committed
226
227
228
229
230
231
232
233
234
235
    if sys.version_info >= (3, 8):
        shutil.copytree('ts/nni_manager/dist', 'nni_node', dirs_exist_ok=True)
    else:
        for item in os.listdir('ts/nni_manager/dist'):
            subsrc = os.path.join('ts/nni_manager/dist', item)
            subdst = os.path.join('nni_node', item)
            if os.path.isdir(subsrc):
                shutil.copytree(subsrc, subdst)
            else:
                shutil.copy2(subsrc, subdst)
liuzhe-lz's avatar
liuzhe-lz committed
236
237
    shutil.copyfile('ts/nni_manager/yarn.lock', 'nni_node/yarn.lock')
    Path('nni_node/nni_manager.tsbuildinfo').unlink()
238
239

    package_json = json.load(open('ts/nni_manager/package.json'))
240
241
242
243
    if version:
        while len(version.split('.')) < 3:  # node.js semver requires at least three parts
            version = version + '.0'
        package_json['version'] = version
244
245
    json.dump(package_json, open('nni_node/package.json', 'w'), indent=2)

246
    # reinstall without development dependencies
247
248
249
250
    _yarn('ts/nni_manager', '--prod', '--cwd', str(Path('nni_node').resolve()))

    shutil.copytree('ts/webui/build', 'nni_node/static')

251
252
253
254
    if jupyter_lab_major_version == '2':
        shutil.copytree('ts/jupyter_extension/build', 'nni_node/jupyter-extension/build')
        shutil.copytree(os.path.join(sys.exec_prefix, 'share/jupyter/lab/extensions'), 'nni_node/jupyter-extension/extensions')
    elif version or Path('ts/jupyter_extension/dist').exists():
255
        shutil.copytree('ts/jupyter_extension/dist', 'nni_node/jupyter-extension')
liuzhe-lz's avatar
liuzhe-lz committed
256

257
258

_yarn_env = dict(os.environ)
259
260
261
# `Path('nni_node').resolve()` does not work on Windows if the directory not exists
_yarn_env['PATH'] = str(Path().resolve() / 'nni_node') + path_env_seperator + os.environ['PATH']
_yarn_path = Path().resolve() / 'toolchain/yarn/bin' / yarn_executable
262
263
264
265
266

def _yarn(path, *args):
    if os.environ.get('GLOBAL_TOOLCHAIN'):
        subprocess.run(['yarn', *args], cwd=path, check=True)
    else:
267
        subprocess.run([str(_yarn_path), *args], cwd=path, check=True, env=_yarn_env)
268
269
270
271
272
273
274
275
276


def _symlink(target_file, link_location):
    target = Path(target_file)
    link = Path(link_location)
    relative = os.path.relpath(target, link.parent)
    link.symlink_to(relative, target.is_dir())


277
278
def _print(*args, color='cyan'):
    color_code = {'yellow': 33, 'cyan': 36}[color]
279
    if sys.platform == 'win32':
280
        print(*args, flush=True)
281
    else:
282
        print(f'\033[1;{color_code}m#', *args, '\033[0m', flush=True)
283
284


285
generated_files = [
286
287
288
289
    'ts/nni_manager/dist',
    'ts/nni_manager/node_modules',
    'ts/webui/build',
    'ts/webui/node_modules',
290
291

    # unit test
liuzhe-lz's avatar
liuzhe-lz committed
292
    'ts/nni_manager/.nyc_output',
293
    'ts/nni_manager/coverage',
294
295
296
    'ts/nni_manager/exp_profile.json',
    'ts/nni_manager/metrics.json',
    'ts/nni_manager/trial_jobs.json',
297
]