[Refactor] refactor mim install (#132)

* refactor(commands/install.py): refactor mim install

* update(commands/install.py): add missing doc comments

* fix(commands/install.py): fix invalid marker during check_install_conflicts and add mminstall.txt cache

* style(commands/mminstall.txt): fix a stupid autoformat

* combine commit: for ci debug

* for ci debug

* commit suggetion(mim/commands/install.py): refine doc comment

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>

* commit suggetion(mim/commands/install.py): refine doc comment

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>

* commit suggestion(mim/commands/install.py): refine doc comments

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>

* commit suggestion(mim/commands/install.py): refine doc comments

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>

* commit suggestion(mim/commands/install.py): refine doc comments

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>

* commit suggestion(mim/commands/install.py): refine doc comments

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>

* chore(requirements): add pip>=19.3

* refine(commands/install.py): refine contextmanager

* fix(commands/install.py): refine tthe cache logic

* fix(commands/install.py): refine the cache logic

combine commit record: for ci debug

* commit suggestion(mim/commands/install.py): refine doc comments

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>

* combine commit record: for ci debug

* refine(commands/install.py): add warning log about index_url passed in install_args

* fix(commands/install.py): refine the cache logic

combine commit record: for ci debug

* doc(tests): add line comments

* feat(commands/install.py): add check_mim_resources

* reload pip._vendor.pkg_resources before install
combine commit record: foc ci debug

* doc(tests): add line comments

* feat(commands/install.py): add check_mim_resources

* doc(tests): add line comments

* doc(tests): add line comments

* feat(commands/install.py): add check_mim_resources

* doc(tests): fix line comments typo

* Revert "Merge branch 'yancong-refactor-install' of github.com:ice-tong/mim into yancong-refactor-install"

This reverts commit 97529af51f, reversing
changes made to e70fa88d64.

* refine comments for importlib.reload(pip._vendor.pkg_resources)

* combine commit: for ci debug

* combine commit record: for ci debug

* feat(commands/install.py): add check_mim_resources

combine commit: for ci debug

* combine commit: for ci debug

* refine comments and remove pyetst cache file

* remove duplicate code snippets

* update(tests/test_install.py): add vcs install case

* fix(tests/test_train.py): fix mmcls install issue

Co-authored-by: Zaida Zhou <58739961+zhouzaida@users.noreply.github.com>
pull/131/head
yancong xiao 2022-06-22 20:09:10 +08:00 committed by GitHub
parent 7421125722
commit 172400dc04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 310 additions and 527 deletions

View File

@ -1,580 +1,348 @@
# Copyright (c) OpenMMLab. All rights reserved.
import importlib
import os
import os.path as osp
import shutil
import tarfile
import tempfile
from distutils.version import LooseVersion
from pkg_resources import parse_requirements, resource_filename
from typing import List
import typing
from contextlib import contextmanager
from typing import Any, Callable, Generator, List, Optional, Tuple
import click
import pip
import pip._vendor.pkg_resources
from pip._internal.commands import create_command
from mim.click import argument, get_official_package, param2lowercase
from mim.commands.uninstall import uninstall
from mim.utils import (
DEFAULT_URL,
MODULE2PKG,
PKG2MODULE,
DEFAULT_CACHE_DIR,
PKG2PROJECT,
WHEEL_URL,
call_command,
echo_success,
echo_warning,
get_installed_version,
get_latest_version,
get_package_version,
get_release_version,
get_torch_cuda_version,
highlighted_error,
is_installed,
is_version_equal,
parse_url,
split_package_version,
)
@click.command('install')
@argument(
'package',
type=str,
autocompletion=get_official_package,
callback=param2lowercase)
@click.command(
'install',
context_settings=dict(ignore_unknown_options=True),
)
@click.argument('args', nargs=-1, type=click.UNPROCESSED)
@click.option(
'-f', '--find', 'find_url', type=str, help='Url for finding package.')
@click.option(
'--default-timeout',
'timeout',
type=int,
default=45,
help='Set the socket timeout (default 15 seconds).')
'-i',
'--index-url',
'--pypi-url',
'index_url',
help='Base URL of the Python Package Index (default %default). '
'This should point to a repository compliant with PEP 503 '
'(the simple repository API) or a local directory laid out '
'in the same format.')
@click.option(
'-y',
'--yes',
'is_yes',
is_flag=True,
help='Dont ask for confirmation of uninstall deletions.')
@click.option(
'--user',
'is_user_dir',
is_flag=True,
help='Install to the Python user install directory')
@click.option(
'-e',
'--editable',
'is_editable',
is_flag=True,
help='Install a package in editable mode.')
help="Don't ask for confirmation of uninstall deletions."
'Deprecated, will have no effect.')
def cli(
package: str,
find_url: str = '',
timeout: int = 30,
args: Tuple[str],
index_url: Optional[str] = None,
is_yes: bool = False,
is_user_dir: bool = False,
is_editable: bool = False,
) -> None:
"""Install package.
"""Install packages.
Example:
You can use `mim install` in the same way you use `pip install`!
And `mim install` will **install the 'mim' extra requirements**
for OpenMMLab packages if needed.
\b
# install latest version of mmcv-full
> mim install mmcv-full # wheel
# install 1.3.1
> mim install mmcv-full==1.3.1
# install master branch
> mim install mmcv-full -f https://github.com/open-mmlab/mmcv.git
Example:
> mim install mmdet mmcls
> mim install git+https://github.com/open-mmlab/mmdetection.git
> mim install -r requirements.txt
> mim install -e <path>
> mim install mmdet -i <url> -f <url>
> mim install mmdet --extra-index-url <url> --trusted-host <hostname>
# install latest version of mmcls
> mim install mmcls
# install 0.11.0
> mim install mmcls==0.11.0 # v0.11.0
# install master branch
> mim install mmcls -f https://github.com/open-mmlab/mmclassification.git
# install local repo
> git clone https://github.com/open-mmlab/mmclassification.git
> cd mmclassification
> mim install .
# install extension based on OpenMMLab
> mim install mmcls-project -f https://github.com/xxx/mmcls-project.git
Here we list several commonly used options.
For more options, please refer to `pip install --help`.
"""
install(
package,
find_url,
timeout,
is_yes=is_yes,
is_user_dir=is_user_dir,
is_editable=is_editable)
if is_yes:
echo_warning(
'The `--yes` option has been deprecated, will have no effect.')
exit_code = install(list(args), index_url=index_url, is_yes=is_yes)
exit(exit_code)
def install(package: str,
find_url: str = '',
timeout: int = 15,
is_yes: bool = False,
is_user_dir: bool = False,
is_editable: bool = False) -> None:
"""Install a package by wheel or from github.
def install(
install_args: List[str],
index_url: Optional[str] = None,
is_yes: bool = False,
) -> Any:
"""Install packages via pip and add 'mim' extra requirements for OpenMMLab
packages during pip install process.
Args:
package (str): The name of installed package, such as mmcls.
find_url (str): Url for finding package. If finding is not provided,
program will infer the find_url as much as possible. Default: ''.
timeout (int): The socket timeout. Default: 15.
is_yes (bool): Dont ask for confirmation of uninstall deletions.
Default: False.
is_usr_dir (bool): Install to the Python user install directory for
environment variables and user configuration. Default: False.
is_editable (bool): Install a package in editable mode. Default: False.
install_args (list): List of arguments passed to `pip install`.
index_url (str, optional): The pypi index url.
is_yes (bool, optional): Deprecated, will have no effect. Reserved for
interface compatibility only.
"""
target_pkg, target_version = split_package_version(package)
# whether install from local repo
if looks_like_path(target_pkg):
if is_installable_dir(target_pkg):
is_install_local_repo = True
else:
raise ValueError(
highlighted_error(
f'{target_pkg} is not a installable directory'))
else:
is_install_local_repo = False
# Reload `pip._vendor.pkg_resources` so that pip can refresh to get the
# latest working set.
# In some cases, when a package is uninstalled and then installed, the
# working set is not updated in time, leading to the mistaken belief that
# the package is already installed.
importlib.reload(pip._vendor.pkg_resources)
# whether install master branch from github
is_install_master = bool(not target_version and find_url)
# add mmcv-full find links by default
install_args += ['-f', get_mmcv_full_find_link()]
# get target version
if target_pkg in PKG2PROJECT:
latest_version = get_latest_version(target_pkg, timeout)
if target_version:
if LooseVersion(target_version) > LooseVersion(latest_version):
error_msg = (f'target_version=={target_version} should not be'
f' greater than latest_version=={latest_version}')
raise ValueError(highlighted_error(error_msg))
else:
target_version = latest_version
index_url_opt_names = ['-i', '--index-url', '--pypi-url']
if any([opt_name in install_args for opt_name in index_url_opt_names]):
echo_warning(
'The index url should be passed in via the index_url parameter, '
'not specified in install_args via -i/--index-url/--pypi-url.')
if index_url is not None:
install_args += ['-i', index_url]
# check local environment whether package existed
if is_install_master or is_install_local_repo:
patch_mm_distribution: Callable = patch_pkg_resources_distribution
try:
# pip>=22.1 have two distribution backends: pkg_resources and importlib. # noqa: E501
from pip._internal.metadata import _should_use_importlib_metadata # type: ignore # isort: skip # noqa: E501
if _should_use_importlib_metadata():
patch_mm_distribution = patch_importlib_distribution
except ImportError:
pass
elif is_installed(target_pkg) and target_version:
existed_version = get_installed_version(target_pkg)
if is_version_equal(existed_version, target_version):
echo_warning(f'{target_pkg}=={existed_version} existed.')
return None
with patch_mm_distribution(index_url):
# We can use `create_command` method since pip>=19.3 (2019/10/14).
status_code = create_command('install').main(install_args)
check_mim_resources()
return status_code
def get_mmcv_full_find_link() -> str:
"""Get the mmcv-full find link corresponding to the current environment."""
torch_v, cuda_v = get_torch_cuda_version()
major, minor, *_ = torch_v.split('.')
torch_v = '.'.join([major, minor, '0'])
if cuda_v.isdigit():
cuda_v = f'cu{cuda_v}'
find_link = WHEEL_URL['mmcv-full'].format(
cuda_version=cuda_v, torch_version=f'torch{torch_v}')
return find_link
@contextmanager
def patch_pkg_resources_distribution(
index_url: Optional[str] = None) -> Generator:
"""A patch for `pip._vendor.pkg_resources.Distribution`.
Since the old version of the OpenMMLab packages did not add the 'mim' extra
requirements to the release distribution, we need to hack the Distribution
and manually fetch the 'mim' requirements from `mminstall.txt`.
This patch works with 'pip<22.1' and 'pip>=22.1,python<3.11'.
Args:
index_url (str, optional): The pypi index url that pass to
`get_mmdeps_from_mmpkg_pypi`.
"""
from pip._vendor.pkg_resources import Distribution, parse_requirements
origin_requires = Distribution.requires
def patched_requires(self, extras=()):
deps = origin_requires(self, extras)
if self.project_name not in PKG2PROJECT or self.project_name == 'mmcv-full': # noqa: E501
return deps
if 'mim' in self.extras:
mim_extra_requires = origin_requires(self, ('mim', ))
filter_invalid_marker(mim_extra_requires)
deps += mim_extra_requires
else:
if is_yes:
uninstall(target_pkg, is_yes)
else:
confirm_msg = (f'{target_pkg}=={existed_version} has been '
f'installed, but want to install {target_pkg}=='
f'{target_version}, do you want to uninstall '
f'{target_pkg}=={existed_version} and '
f'install {target_pkg}=={target_version}? ')
if click.confirm(confirm_msg):
uninstall(target_pkg, True)
else:
echo_warning(f'skip {target_pkg}')
return None
if not hasattr(self, '_mm_deps'):
assert self.version is not None
mmdeps_text = get_mmdeps_from_mmpkg(self.project_name,
self.version, index_url)
self._mm_deps = list(parse_requirements(mmdeps_text))
echo_warning(
"Get 'mim' extra requirements from `mminstall.txt` "
f'for {self}: {[str(i) for i in self._mm_deps]}.')
deps += self._mm_deps
return deps
# try to infer find_url if possible
if not find_url:
find_url = infer_find_url(target_pkg)
Distribution.requires = patched_requires # type: ignore
try:
yield
finally:
Distribution.requires = origin_requires # type: ignore
if is_install_local_repo:
repo_root = osp.abspath(target_pkg)
module_name, target_version = get_package_version(repo_root)
if not module_name:
raise FileNotFoundError(
highlighted_error(f'version.py is missed in {repo_root}'))
target_pkg = MODULE2PKG.get(module_name, module_name)
if target_pkg == 'mmcv' and os.getenv('MMCV_WITH_OPS', '0') == '1':
target_pkg = 'mmcv-full'
@contextmanager
def patch_importlib_distribution(index_url: Optional[str] = None) -> Generator:
"""A patch for `pip._internal.metadata.importlib.Distribution`.
echo_success(f'installing {target_pkg} from local repo.')
Since the old version of the OpenMMLab packages did not add the 'mim' extra
requirements to the release distribution, we need to hack the Distribution
and manually fetch the 'mim' requirements from `mminstall.txt`.
install_from_repo(
repo_root,
package=target_pkg,
timeout=timeout,
is_yes=is_yes,
is_user_dir=is_user_dir,
is_editable=is_editable)
This patch works with 'pip>=22.1,python>=3.11'.
elif find_url and find_url.find('git') >= 0 or is_install_master:
install_from_github(target_pkg, target_version, find_url, timeout,
is_yes, is_user_dir, is_install_master)
else:
# if installing from wheel failed, it will try to install package by
# building from source if possible.
Args:
index_url (str, optional): The pypi index url that pass to
`get_mminstall_from_pypi`.
"""
from pip._internal.metadata.importlib import Distribution
from pip._internal.metadata.importlib._dists import Requirement
origin_iter_dependencies = Distribution.iter_dependencies
def patched_iter_dependencies(self, extras=()):
deps = list(origin_iter_dependencies(self, extras))
if self.canonical_name not in PKG2PROJECT or self.canonical_name == 'mmcv-full': # noqa: E501
return deps
if 'mim' in self.iter_provided_extras:
mim_extra_requires = list(
origin_iter_dependencies(self, ('mim', )))
filter_invalid_marker(mim_extra_requires)
deps += mim_extra_requires
else:
if not hasattr(self, '_mm_deps'):
assert self.version is not None
mmdeps_text = get_mmdeps_from_mmpkg(self.canonical_name,
self.version, index_url)
self._mm_deps = [
Requirement(req) for req in mmdeps_text.splitlines()
]
deps += self._mm_deps
return deps
Distribution.iter_dependencies = patched_iter_dependencies
try:
yield
finally:
Distribution.iter_dependencies = origin_iter_dependencies
def filter_invalid_marker(extra_requires: List) -> None:
"""Filter out invalid marker in requirements parsed from METADATA.
More detail can be found at: https://github.com/pypa/pip/issues/11191
Args:
extra_requires (list): A list of Requirement parsed from distribution
METADATA.
"""
for req in extra_requires:
if req.marker is None:
continue
try:
install_from_wheel(target_pkg, target_version, find_url, timeout,
is_user_dir)
except RuntimeError as error:
if target_pkg in PKG2PROJECT:
find_url = f'{DEFAULT_URL}/{PKG2PROJECT[target_pkg]}.git'
if target_version:
target_pkg = f'{target_pkg}=={target_version}'
if is_yes:
install(target_pkg, find_url, timeout, is_yes, is_user_dir)
else:
confirm_msg = (f'install {target_pkg} from wheel, but it '
'failed. Do you want to build it from '
'source if possible?')
if click.confirm(confirm_msg):
install(target_pkg, find_url, timeout, is_yes,
is_user_dir)
else:
raise RuntimeError(
highlighted_error(
f'Failed to install {target_pkg}.'))
else:
raise RuntimeError(highlighted_error(error))
echo_success(f'Successfully installed {target_pkg}.')
req.marker.evaluate()
except: # noqa: E722
req.marker = None
def looks_like_path(name: str) -> bool:
"""Checks whether the string "looks like" a path on the filesystem.
def get_mmdeps_from_mmpkg(mmpkg_name: str,
mmpkg_version: str,
index_url: Optional[str] = None) -> str:
"""Get 'mim' extra requirements for a given OpenMMLab package from
`mminstall.txt`.
This does not check whether the target actually exists, only judge from the
appearance.
If there is a cached `mminstall.txt`, use the cache, otherwise download the
source distribution package from pypi and extract `mminstall.txt` content.
Args:
name (str): The string to be checked.
mmpkg_name (str): The OpenMMLab package name.
mmpkg_version (str): The OpenMMLab package version.
index_url (str, optional): The pypi index url that pass to
`get_mminstall_from_pypi`.
Returns:
str: The text content read from `mminstall.txt`, returns an empty
string if anything goes wrong.
"""
if osp.sep in name:
return True
if osp.altsep is not None and osp.altsep in name:
return True
if name.startswith('.'):
return True
return False
def is_installable_dir(name: str) -> bool:
"""Check whether path is a directory containing setup.py.
Args:
name (str): The string to be checked.
"""
path = osp.abspath(name)
if osp.isdir(path):
setup_py = osp.join(path, 'setup.py')
return osp.isfile(setup_py)
mmpkg = f'{mmpkg_name}=={mmpkg_version}'
cache_mminstall_dir = os.path.join(DEFAULT_CACHE_DIR, 'mminstall')
if not os.path.exists(cache_mminstall_dir):
os.mkdir(cache_mminstall_dir)
cache_mminstall_fpath = os.path.join(cache_mminstall_dir, f'{mmpkg}.txt')
if os.path.exists(cache_mminstall_fpath):
# use cached `mminstall.txt`
with open(cache_mminstall_fpath) as f:
mminstall_content = f.read()
echo_warning(
f'Using cached `mminstall.txt` for {mmpkg}: {cache_mminstall_fpath}' # noqa: E501
)
else:
return False
# fetch `mminstall.txt` content from pypi
mminstall_content = get_mminstall_from_pypi(mmpkg, index_url=index_url)
with open(cache_mminstall_fpath, 'w') as f:
f.write(mminstall_content)
return mminstall_content
def infer_find_url(package: str) -> str:
"""Try to infer find_url if possible.
If package is the official package, the find_url can be inferred.
@typing.no_type_check
def get_mminstall_from_pypi(mmpkg: str,
index_url: Optional[str] = None) -> str:
"""Get the `mminstall.txt` content for a given OpenMMLab package from PyPi.
Args:
package (str): The name of package, such as mmcls.
mmpkg (str): The OpenMMLab package name, optionally with a version
specifier. e.g. 'mmdet', 'mmdet==2.25.0'.
index_url (str, optional): The pypi index url, if given, will be used
in `pip download`.
Returns:
str: The text content read from `mminstall.txt`, returns an empty
string if anything goes wrong.
"""
find_url = ''
if package in WHEEL_URL:
torch_v, cuda_v = get_torch_cuda_version()
# In order to avoid builiding mmcv-full from source, we ignore the
# difference among micro version because there are usually no big
# changes among micro version. For example, the mmcv-full built in
# pytorch 1.8.0 also works on 1.8.1 or other versions.
major, minor, *_ = torch_v.split('.')
torch_v = '.'.join([major, minor, '0'])
if cuda_v.isdigit():
cuda_v = f'cu{cuda_v}'
find_url = WHEEL_URL[package].format(
cuda_version=cuda_v, torch_version=f'torch{torch_v}')
elif package in PKG2PROJECT:
find_url = (f'{DEFAULT_URL}/{PKG2PROJECT[package]}.git')
return find_url
def parse_dependencies(path: str) -> list:
"""Parse dependencies from repo/requirements/mminstall.txt.
Args:
path (str): Path of mminstall.txt.
"""
def _get_proper_version(package, version, op):
releases = get_release_version(package)
if op == '>':
for r_v in releases:
if LooseVersion(r_v) > LooseVersion(version):
return r_v
else:
raise ValueError(
highlighted_error(f'invalid min version of {package}'))
elif op == '<':
for r_v in releases[::-1]:
if LooseVersion(r_v) < LooseVersion(version):
return r_v
else:
raise ValueError(
highlighted_error(f'invalid max version of {package}'))
dependencies = []
with open(path) as fr:
for requirement in parse_requirements(fr):
pkg_name = requirement.project_name
min_version = ''
max_version = ''
for op, version in requirement.specs:
if op == '==':
min_version = max_version = version
break
elif op == '>=':
min_version = version
elif op == '>':
min_version = _get_proper_version(pkg_name, version, '>')
elif op == '<=':
max_version = version
elif op == '<':
max_version = _get_proper_version(pkg_name, version, '<')
dependencies.append([pkg_name, min_version, max_version])
return dependencies
def install_dependencies(dependencies: List[List[str]],
timeout: int = 15,
is_yes: bool = False,
is_user_dir: bool = False) -> None:
"""Install dependencies, such as mmcls depends on mmcv.
Args:
dependencies (list): The list of dependency.
timeout (int): The socket timeout. Default: 15.
is_yes (bool): Dont ask for confirmation of uninstall deletions.
Default: False.
is_usr_dir (bool): Install to the Python user install directory for
environment variables and user configuration. Default: False.
"""
for target_pkg, min_v, max_v in dependencies:
target_version = max_v
latest_version = get_latest_version(target_pkg, timeout)
if not target_version or LooseVersion(target_version) > LooseVersion(
latest_version):
target_version = latest_version
if is_installed(target_pkg):
existed_version = get_installed_version(target_pkg)
if (LooseVersion(min_v) <= LooseVersion(existed_version) <=
LooseVersion(target_version)):
continue
echo_success(f'installing dependency: {target_pkg}')
target_pkg = f'{target_pkg}=={target_version}'
install(
target_pkg,
timeout=timeout,
is_yes=is_yes,
is_user_dir=is_user_dir)
echo_success('Successfully installed dependencies.')
def install_from_repo(repo_root: str,
*,
package: str = '',
timeout: int = 15,
is_yes: bool = False,
is_user_dir: bool = False,
is_editable: bool = False):
"""Install package from local repo.
Args:
repo_root (str): The root of repo.
package (str): The name of installed package. Default: ''.
timeout (int): The socket timeout. Default: 15.
is_yes (bool): Dont ask for confirmation of uninstall deletions.
Default: False.
is_usr_dir (bool): Install to the Python user install directory for
environment variables and user configuration. Default: False.
is_editable (bool): Install a package in editable mode. Default: False.
"""
def copy_file_to_package():
# rename the model_zoo.yml to model-index.yml but support both of them
# for backward compatibility
filenames = ['tools', 'configs', 'model_zoo.yml', 'model-index.yml']
module_name = PKG2MODULE.get(package, package)
# configs, tools and model-index.yml will be copied to package/.mim
mim_root = resource_filename(module_name, '.mim')
os.makedirs(mim_root, exist_ok=True)
for filename in filenames:
src_path = osp.join(repo_root, filename)
dst_path = osp.join(mim_root, filename)
if osp.exists(src_path):
if osp.islink(dst_path):
os.unlink(dst_path)
if osp.isfile(src_path):
shutil.copyfile(src_path, dst_path)
elif osp.isdir(src_path):
if osp.exists(dst_path):
shutil.rmtree(dst_path)
shutil.copytree(src_path, dst_path)
def link_file_to_package():
# When user installs package with editable mode, we should create
# symlinks to package, which will synchronize the modified files.
# Besides, rename the model_zoo.yml to model-index.yml but support both
# of them for backward compatibility
filenames = ['tools', 'configs', 'model_zoo.yml', 'model-index.yml']
module_name = PKG2MODULE.get(package, package)
pkg_root = osp.join(repo_root, module_name)
# configs, tools and model-index.yml will be linked to package/.mim
mim_root = osp.join(pkg_root, '.mim')
os.makedirs(mim_root, exist_ok=True)
for filename in filenames:
src_path = osp.join(repo_root, filename)
dst_path = osp.join(mim_root, filename)
if osp.exists(dst_path):
continue
if osp.exists(src_path):
if osp.isfile(dst_path) or osp.islink(dst_path):
os.remove(dst_path)
elif osp.isdir(dst_path):
shutil.rmtree(dst_path)
os.symlink(src_path, dst_path)
# install dependencies. For example,
# install mmcls should install mmcv-full first if it is not installed or
# its(mmcv) version does not match.
mminstall_path = osp.join(repo_root, 'requirements', 'mminstall.txt')
if osp.exists(mminstall_path):
dependencies = parse_dependencies(mminstall_path)
if dependencies:
install_dependencies(dependencies, timeout, is_yes, is_user_dir)
third_dependencies = osp.join(repo_root, 'requirements', 'build.txt')
if osp.exists(third_dependencies):
dep_cmd = [
'python', '-m', 'pip', 'install', '-r', third_dependencies,
'--default-timeout', f'{timeout}'
with tempfile.TemporaryDirectory() as temp_dir:
download_args = [
mmpkg, '-d', temp_dir, '--no-deps', '--no-binary', ':all:'
]
if is_user_dir:
dep_cmd.append('--user')
call_command(dep_cmd)
install_cmd = ['python', '-m', 'pip', 'install']
if is_editable:
install_cmd.append('-e')
else:
# solving issues related to out-of-tree builds
# more datails at https://github.com/pypa/pip/issues/7555
# starting from pip==21.3 in-tree builds are the default
# and --use-feature=in-tree-build is ignored
# more details at https://pip.pypa.io/en/stable/news/#id82
# starting from pip==22.1 --use-feature=in-tree-build raises an error
if LooseVersion('21.1.1') <= LooseVersion(
pip.__version__) < LooseVersion('22.1'):
install_cmd.append('--use-feature=in-tree-build')
install_cmd.append(repo_root)
if is_user_dir:
install_cmd.append('--user')
# The issue is caused by the import order of numpy and torch
# Please refer to github.com/pytorch/pytorch/issue/37377
os.environ['MKL_SERVICE_FORCE_INTEL'] = '1'
if package in WHEEL_URL:
echo_success(f'compiling {package} with "MMCV_WITH_OPS=1"')
os.environ['MMCV_WITH_OPS'] = '1'
call_command(install_cmd)
if is_editable:
link_file_to_package()
else:
copy_file_to_package()
if index_url is not None:
download_args += ['-i', index_url]
status_code = create_command('download').main(download_args)
if status_code != 0:
echo_warning(f'pip download failed with args: {download_args}')
exit(status_code)
mmpkg_tar_fpath = os.path.join(temp_dir, os.listdir(temp_dir)[0])
with tarfile.open(mmpkg_tar_fpath) as tarf:
mmdeps_fpath = tarf.members[0].name + '/requirements/mminstall.txt'
mmdeps_member = tarf._getmember(name=mmdeps_fpath)
if mmdeps_member is None:
echo_warning(f'{mmdeps_fpath} not found in {mmpkg_tar_fpath}')
return ''
tarf.fileobj.seek(mmdeps_member.offset_data)
mmdeps_content = tarf.fileobj.read(mmdeps_member.size).decode()
return mmdeps_content
def install_from_github(package: str,
version: str = '',
find_url: str = '',
timeout: int = 15,
is_yes: bool = False,
is_user_dir: bool = False,
is_install_master: bool = False) -> None:
"""Install package from github.
def check_mim_resources() -> None:
"""Check if the mim resource directory exists.
Args:
package (str): The name of installed package, such as mmcls.
version (str): Version of package. Default: ''.
find_url (str): Url for finding package. If finding is not provided,
program will infer the find_url as much as possible. Default: ''.
timeout (int): The socket timeout. Default: 15.
is_yes (bool): Dont ask for confirmation of uninstall deletions.
Default: False.
is_usr_dir (bool): Install to the Python user install directory for
environment variables and user configuration. Default: False.
is_install_master (bool): Whether install master branch. If it is True,
process will install master branch. If it is False, process will
install the specified version. Default: False.
Newer versions of the OpenMMLab packages have packaged the mim resource
files into the distribution package, while earlier versions do not.
If the mim resources file (aka `.mim`) do not exists, log a warning that a
new version needs to be installed.
"""
click.echo(f'installing {package} from {find_url}.')
_, repo = parse_url(find_url)
clone_cmd = ['git', 'clone', find_url]
if not is_install_master:
clone_cmd.extend(['-b', f'v{version}'])
with tempfile.TemporaryDirectory() as temp_root:
repo_root = osp.join(temp_root, repo)
clone_cmd.append(repo_root)
call_command(clone_cmd)
install_from_repo(
repo_root,
package=package,
timeout=timeout,
is_yes=is_yes,
is_user_dir=is_user_dir)
def install_from_wheel(package: str,
version: str = '',
find_url: str = '',
timeout: int = 15,
is_user_dir: bool = False) -> None:
"""Install wheel from find_url.
Args:
package (str): The name of installed package, such as mmcls.
version (str): Version of package. Default: ''.
find_url (str): Url for finding package. If finding is not provided,
program will infer the find_url as much as possible. Default: ''.
timeout (int): The socket timeout. Default: 15.
is_usr_dir (bool): Install to the Python user install directory for
environment variables and user configuration. Default: False.
"""
click.echo(f'installing {package} from wheel.')
install_cmd = [
'python', '-m', 'pip', '--default-timeout', f'{timeout}', 'install'
]
if version:
install_cmd.append(f'{package}=={version}')
else:
install_cmd.append(package)
if find_url:
install_cmd.extend(['-f', find_url])
if is_user_dir:
install_cmd.append('--user')
call_command(install_cmd)
importlib.reload(pip._vendor.pkg_resources)
for pkg in pip._vendor.pkg_resources.working_set: # type: ignore
pkg_name = pkg.project_name
if pkg_name not in PKG2PROJECT or pkg_name == 'mmcv-full':
continue
if pkg.has_metadata('top_level.txt'):
module_name = pkg.get_metadata('top_level.txt').split('\n')[0]
installed_path = os.path.join(pkg.location, module_name)
else:
installed_path = os.path.join(pkg.location, pkg_name)
mim_resources_path = os.path.join(installed_path, '.mim')
if not os.path.exists(mim_resources_path):
echo_warning(f'mim resources not found: {mim_resources_path}, '
f'you can try to install the latest {pkg_name}.')

View File

@ -2,5 +2,6 @@ Click
colorama
model-index
pandas
pip>=19.3
requests
tabulate

View File

@ -28,6 +28,10 @@ def test_gridsearch(gpus, tmp_path):
runner = CliRunner()
result = runner.invoke(install, ['mmcls', '--yes'])
assert result.exit_code == 0
# Since `mminstall.txt` is not included in the distribution of
# mmcls<=0.23.1, we need to install mmcv-full manually.
result = runner.invoke(install, ['mmcv-full', '--yes'])
assert result.exit_code == 0
args1 = [
'mmcls', 'tests/data/lenet5_mnist.py', f'--gpus={gpus}',

View File

@ -71,17 +71,15 @@ def test_mmrepo_install():
os.chdir(current_root)
# mim install git+https://github.com/open-mmlab/mmclassification.git
result = runner.invoke(
install, ['git+https://github.com/open-mmlab/mmclassification.git'])
assert result.exit_code == 0
# mim install mmcls --yes
result = runner.invoke(install, ['mmcls', '--yes'])
assert result.exit_code == 0
# mim install mmcls -f https://github.com/open-mmlab/mmclassification.git
# install master branch
result = runner.invoke(install, [
'mmcls', '--yes', '-f',
'https://github.com/open-mmlab/mmclassification.git'
])
# mim install mmcls==0.11.0 --yes
result = runner.invoke(install, ['mmcls==0.11.0', '--yes'])
assert result.exit_code == 0

View File

@ -16,11 +16,11 @@ def setup_module():
def test_list():
runner = CliRunner()
# mim install mmcls==0.12.0 --yes
result = runner.invoke(install, ['mmcls==0.12.0', '--yes'])
# mim install mmcls==0.23.0 --yes
result = runner.invoke(install, ['mmcls==0.23.0', '--yes'])
assert result.exit_code == 0
# mim list
target = ('mmcls', '0.12.0',
target = ('mmcls', '0.23.0',
'https://github.com/open-mmlab/mmclassification')
result = list_package()
assert target in result

View File

@ -28,6 +28,10 @@ def test_run(device, gpus, tmp_path):
runner = CliRunner()
result = runner.invoke(install, ['mmcls', '--yes'])
assert result.exit_code == 0
# Since `mminstall.txt` is not included in the distribution of
# mmcls<=0.23.1, we need to install mmcv-full manually.
result = runner.invoke(install, ['mmcv-full', '--yes'])
assert result.exit_code == 0
result = runner.invoke(run, [
'mmcls', 'train', 'tests/data/lenet5_mnist.py', f'--gpus={gpus}',

View File

@ -27,6 +27,10 @@ def test_test(device):
runner = CliRunner()
result = runner.invoke(install, ['mmcls', '--yes'])
assert result.exit_code == 0
# Since `mminstall.txt` is not included in the distribution of
# mmcls<=0.23.1, we need to install mmcv-full manually.
result = runner.invoke(install, ['mmcv-full', '--yes'])
assert result.exit_code == 0
result = runner.invoke(test, [
'mmcls', 'tests/data/lenet5_mnist.py', '--checkpoint',

View File

@ -27,6 +27,10 @@ def test_train(gpus, tmp_path):
runner = CliRunner()
result = runner.invoke(install, ['mmcls', '--yes'])
assert result.exit_code == 0
# Since `mminstall.txt` is not included in the distribution of
# mmcls<=0.23.1, we need to install mmcv-full manually.
result = runner.invoke(install, ['mmcv-full', '--yes'])
assert result.exit_code == 0
result = runner.invoke(train, [
'mmcls', 'tests/data/lenet5_mnist.py', f'--gpus={gpus}',