Git fetching: add option to remove submodules (#14370)

Add an optional 'submodules_delete' field to Git versions in Spack
packages that allows them to remove specific submodules.

For example: the nervanagpu submodule has become unavailable for the
PyTorch project (see issue 19457 at
https://github.com/pytorch/pytorch/issues/). Removing this submodule
allows 0.4.1 to build.
This commit is contained in:
Andrew W Elble 2020-02-03 22:02:45 -05:00 committed by GitHub
parent 7d444f08e7
commit 4accc78409
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 5 deletions

View file

@ -929,6 +929,9 @@ Git fetching supports the following parameters to ``version``:
* ``tag``: Name of a tag to fetch. * ``tag``: Name of a tag to fetch.
* ``commit``: SHA hash (or prefix) of a commit to fetch. * ``commit``: SHA hash (or prefix) of a commit to fetch.
* ``submodules``: Also fetch submodules recursively when checking out this repository. * ``submodules``: Also fetch submodules recursively when checking out this repository.
* ``submodules_delete``: A list of submodules to forcibly delete from the repository
after fetching. Useful if a version in the repository has submodules that
have disappeared/are no longer accessible.
* ``get_full_repo``: Ensure the full git history is checked out with all remote * ``get_full_repo``: Ensure the full git history is checked out with all remote
branch information. Normally (``get_full_repo=False``, the default), the git branch information. Normally (``get_full_repo=False``, the default), the git
option ``--depth 1`` will be used if the version of git and the specified option ``--depth 1`` will be used if the version of git and the specified

View file

@ -714,7 +714,8 @@ class GitFetchStrategy(VCSFetchStrategy):
Repositories are cloned into the standard stage source path directory. Repositories are cloned into the standard stage source path directory.
""" """
url_attr = 'git' url_attr = 'git'
optional_attrs = ['tag', 'branch', 'commit', 'submodules', 'get_full_repo'] optional_attrs = ['tag', 'branch', 'commit', 'submodules',
'get_full_repo', 'submodules_delete']
def __init__(self, **kwargs): def __init__(self, **kwargs):
# Discards the keywords in kwargs that may conflict with the next call # Discards the keywords in kwargs that may conflict with the next call
@ -725,6 +726,7 @@ def __init__(self, **kwargs):
self._git = None self._git = None
self.submodules = kwargs.get('submodules', False) self.submodules = kwargs.get('submodules', False)
self.submodules_delete = kwargs.get('submodules_delete', False)
self.get_full_repo = kwargs.get('get_full_repo', False) self.get_full_repo = kwargs.get('get_full_repo', False)
@property @property
@ -858,6 +860,14 @@ def fetch(self):
git(*pull_args, ignore_errors=1) git(*pull_args, ignore_errors=1)
git(*co_args) git(*co_args)
if self.submodules_delete:
with working_dir(self.stage.source_path):
for submodule_to_delete in self.submodules_delete:
args = ['rm', submodule_to_delete]
if not spack.config.get('config:debug'):
args.insert(1, '--quiet')
git(*args)
# Init submodules if the user asked for them. # Init submodules if the user asked for them.
if self.submodules: if self.submodules:
with working_dir(self.stage.source_path): with working_dir(self.stage.source_path):

View file

@ -744,11 +744,31 @@ def mock_archive(request, tmpdir_factory):
@pytest.fixture(scope='session') @pytest.fixture(scope='session')
def mock_git_repository(tmpdir_factory): def mock_git_repository(tmpdir_factory):
"""Creates a very simple git repository with two branches and """Creates a simple git repository with two branches,
two commits. two commits and two submodules. Each submodule has one commit.
""" """
git = spack.util.executable.which('git', required=True) git = spack.util.executable.which('git', required=True)
suburls = []
for submodule_count in range(2):
tmpdir = tmpdir_factory.mktemp('mock-git-repo-submodule-dir-{0}'
.format(submodule_count))
tmpdir.ensure(spack.stage._source_path_subdir, dir=True)
repodir = tmpdir.join(spack.stage._source_path_subdir)
suburls.append((submodule_count, 'file://' + str(repodir)))
# Initialize the repository
with repodir.as_cwd():
git('init')
git('config', 'user.name', 'Spack')
git('config', 'user.email', 'spack@spack.io')
# r0 is just the first commit
submodule_file = 'r0_file_{0}'.format(submodule_count)
repodir.ensure(submodule_file)
git('add', submodule_file)
git('commit', '-m', 'mock-git-repo r0 {0}'.format(submodule_count))
tmpdir = tmpdir_factory.mktemp('mock-git-repo-dir') tmpdir = tmpdir_factory.mktemp('mock-git-repo-dir')
tmpdir.ensure(spack.stage._source_path_subdir, dir=True) tmpdir.ensure(spack.stage._source_path_subdir, dir=True)
repodir = tmpdir.join(spack.stage._source_path_subdir) repodir = tmpdir.join(spack.stage._source_path_subdir)
@ -759,6 +779,9 @@ def mock_git_repository(tmpdir_factory):
git('config', 'user.name', 'Spack') git('config', 'user.name', 'Spack')
git('config', 'user.email', 'spack@spack.io') git('config', 'user.email', 'spack@spack.io')
url = 'file://' + str(repodir) url = 'file://' + str(repodir)
for number, suburl in suburls:
git('submodule', 'add', suburl,
'third_party/submodule{0}'.format(number))
# r0 is just the first commit # r0 is just the first commit
r0_file = 'r0_file' r0_file = 'r0_file'

View file

@ -19,7 +19,6 @@
from spack.fetch_strategy import GitFetchStrategy from spack.fetch_strategy import GitFetchStrategy
from spack.util.executable import which from spack.util.executable import which
pytestmark = pytest.mark.skipif( pytestmark = pytest.mark.skipif(
not which('git'), reason='requires git to be installed') not which('git'), reason='requires git to be installed')
@ -217,3 +216,59 @@ def test_get_full_repo(get_full_repo, git_version, mock_git_repository,
else: else:
assert(nbranches == 2) assert(nbranches == 2)
assert(ncommits == 1) assert(ncommits == 1)
@pytest.mark.disable_clean_stage_check
@pytest.mark.parametrize("submodules", [True, False])
def test_gitsubmodule(submodules, mock_git_repository, config,
mutable_mock_repo):
"""
Test GitFetchStrategy behavior with submodules
"""
type_of_test = 'tag-branch'
t = mock_git_repository.checks[type_of_test]
# Construct the package under test
spec = Spec('git-test')
spec.concretize()
pkg = spack.repo.get(spec)
args = copy.copy(t.args)
args['submodules'] = submodules
pkg.versions[ver('git')] = args
pkg.do_stage()
with working_dir(pkg.stage.source_path):
for submodule_count in range(2):
file_path = os.path.join(pkg.stage.source_path,
'third_party/submodule{0}/r0_file_{0}'
.format(submodule_count))
if submodules:
assert os.path.isfile(file_path)
else:
assert not os.path.isfile(file_path)
@pytest.mark.disable_clean_stage_check
def test_gitsubmodules_delete(mock_git_repository, config, mutable_mock_repo):
"""
Test GitFetchStrategy behavior with submodules_delete
"""
type_of_test = 'tag-branch'
t = mock_git_repository.checks[type_of_test]
# Construct the package under test
spec = Spec('git-test')
spec.concretize()
pkg = spack.repo.get(spec)
args = copy.copy(t.args)
args['submodules'] = True
args['submodules_delete'] = ['third_party/submodule0',
'third_party/submodule1']
pkg.versions[ver('git')] = args
pkg.do_stage()
with working_dir(pkg.stage.source_path):
file_path = os.path.join(pkg.stage.source_path,
'third_party/submodule0')
assert not os.path.isdir(file_path)
file_path = os.path.join(pkg.stage.source_path,
'third_party/submodule1')
assert not os.path.isdir(file_path)

View file

@ -58,7 +58,8 @@ class PyTorch(PythonPackage, CudaPackage):
version('1.1.0', tag='v1.1.0', submodules=True) version('1.1.0', tag='v1.1.0', submodules=True)
version('1.0.1', tag='v1.0.1', submodules=True) version('1.0.1', tag='v1.0.1', submodules=True)
version('1.0.0', tag='v1.0.0', submodules=True) version('1.0.0', tag='v1.0.0', submodules=True)
version('0.4.1', tag='v0.4.1', submodules=True) version('0.4.1', tag='v0.4.1', submodules=True,
submodules_delete=['third_party/nervanagpu'])
version('0.4.0', tag='v0.4.0', submodules=True) version('0.4.0', tag='v0.4.0', submodules=True)
version('0.3.1', tag='v0.3.1', submodules=True) version('0.3.1', tag='v0.3.1', submodules=True)