Skip to content

Commit 48ef453

Browse files
committed
Extract installation of dependencies from PackageBase.do_install
Up to this commit PackageBase.do_install was installing the root package of a DAG unconditionally and its dependencies conditionally (depending on the value of the boolean argument 'install_deps'). However, a sensible operation in many contexts is to install *only* the dependencies. This was not possible via API, and the code to do that was replicated in the implementation of PackageBase.do_install and of the 'spack install' command. Further, the code in those two places was not doing *exactly* the same thing. This commit extracts a new function that is used in both places, and is external to PackageBase. This choice has been preferred over extending PackageBase.do_install API because the method was already complex enough (10-15 arguments) and needed to be simplified + having a method on an instance that installs anything but that instance spec didn't seem a sensible choice.
1 parent 229db0f commit 48ef453

11 files changed

Lines changed: 107 additions & 107 deletions

File tree

lib/spack/spack/cmd/bootstrap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import spack
2727
import spack.cmd
2828
import spack.cmd.common.arguments as arguments
29+
import spack.package
2930

3031
description = "Bootstrap packages needed for spack to run smoothly"
3132
section = "admin"
@@ -86,5 +87,4 @@ def bootstrap(parser, args, **kwargs):
8687
tty.msg("Installing %s to satisfy requirement for %s" %
8788
(spec_to_install, requirement))
8889
kwargs['explicit'] = True
89-
package = spack.repo.get(spec_to_install)
90-
package.do_install(**kwargs)
90+
spack.package.install(spec_to_install, **kwargs)

lib/spack/spack/cmd/configure.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ def _stop_at_phase_during_install(args, calling_fn, phase_mapping):
8686
inst.install(parser, install_args)
8787
# Install package and stop at the given phase
8888
cli_args = ['-v'] if args.verbose else []
89-
install_args = parser.parse_args(cli_args + ['--only=package'])
89+
install_args = parser.parse_args(cli_args + ['--only=root'])
9090
install_args.package = args.package
9191
inst.install(parser, install_args, stop_at=phase)
9292
except IndexError:

lib/spack/spack/cmd/diy.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
# License along with this program; if not, write to the Free Software
2323
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2424
##############################################################################
25-
import sys
2625
import os
2726
import argparse
2827

@@ -78,22 +77,23 @@ def diy(self, args):
7877
"Did you forget a package version number?")
7978

8079
spec.concretize()
81-
package = spack.repo.get(spec)
8280

83-
if package.installed:
84-
tty.error("Already installed in %s" % package.prefix)
85-
tty.msg("Uninstall or try adding a version suffix for this DIY build.")
86-
sys.exit(1)
81+
if spec.package.installed:
82+
msg = "Already installed in {0}. "
83+
msg += "Uninstall or try adding a version suffix for this DIY build."
84+
tty.die(msg.format(spec.prefix))
8785

8886
# Forces the build to run out of the current directory.
89-
package.stage = DIYStage(os.getcwd())
87+
spec.package.stage = DIYStage(os.getcwd())
9088

9189
# TODO: make this an argument, not a global.
9290
spack.do_checksum = False
9391

94-
package.do_install(
92+
spack.package.install(
93+
spec,
9594
keep_prefix=args.keep_prefix,
9695
install_deps=not args.ignore_deps,
9796
verbose=not args.quiet,
9897
keep_stage=True, # don't remove source dir for DIY.
99-
dirty=args.dirty)
98+
dirty=args.dirty
99+
)

lib/spack/spack/cmd/install.py

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@
5050
def setup_parser(subparser):
5151
subparser.add_argument(
5252
'--only',
53-
default='package,dependencies',
53+
default='root,dependencies',
5454
dest='things_to_install',
55-
choices=['package', 'dependencies'],
55+
choices=['root', 'dependencies'],
5656
help="""select the mode of installation.
5757
the default is to install the package along with all its dependencies.
5858
alternatively one can decide to install only the package or only
@@ -344,19 +344,8 @@ def install_spec(cli_args, kwargs, spec):
344344
try:
345345
# decorate the install if necessary
346346
PackageBase.do_install = decorator(PackageBase.do_install)
347-
348-
if cli_args.things_to_install == 'dependencies':
349-
# Install dependencies as-if they were installed
350-
# for root (explicit=False in the DB)
351-
kwargs['explicit'] = False
352-
for s in spec.dependencies():
353-
p = spack.repo.get(s)
354-
p.do_install(**kwargs)
355-
else:
356-
package = spack.repo.get(spec)
357-
kwargs['explicit'] = True
358-
package.do_install(**kwargs)
359-
347+
what = cli_args.things_to_install.split(',')
348+
spack.package.install(spec, what=what, **kwargs)
360349
except InstallError as e:
361350
if cli_args.show_log_on_error:
362351
e.print_context()

lib/spack/spack/package.py

Lines changed: 61 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
from six import with_metaclass
4949

5050
import llnl.util.tty as tty
51+
import llnl.util.filesystem as filesystem
5152
import spack
5253
import spack.store
5354
import spack.compilers
@@ -63,7 +64,7 @@
6364
import spack.binary_distribution as binary_distribution
6465

6566
from llnl.util.filesystem import mkdirp, join_path, touch, ancestor
66-
from llnl.util.filesystem import working_dir, install_tree, install
67+
from llnl.util.filesystem import working_dir, install_tree
6768
from llnl.util.lang import memoized
6869
from llnl.util.link_tree import LinkTree
6970
from llnl.util.tty.log import log_output
@@ -552,6 +553,9 @@ class SomePackage(Package):
552553
#: index of patches by sha256 sum, built lazily
553554
_patches_by_hash = None
554555

556+
#: Used to stop early during installation, if set in instances
557+
last_phase = None
558+
555559
#: List of strings which contains GitHub usernames of package maintainers.
556560
#: Do not include @ here in order not to unnecessarily ping the users.
557561
maintainers = []
@@ -1287,18 +1291,18 @@ def do_install(self,
12871291
keep_prefix=False,
12881292
keep_stage=False,
12891293
install_source=False,
1290-
install_deps=True,
12911294
skip_patch=False,
12921295
verbose=False,
12931296
make_jobs=None,
12941297
fake=False,
12951298
explicit=False,
12961299
dirty=None,
12971300
**kwargs):
1298-
"""Called by commands to install a package and its dependencies.
1301+
"""Called by :py:func:`install` to install *this* package.
12991302
1300-
Package implementations should override install() to describe
1301-
their build process.
1303+
This method installs only ``self.spec`` and assumes all its
1304+
dependencies are already installed and the spec itself is
1305+
concrete.
13021306
13031307
Args:
13041308
keep_prefix (bool): Keep install prefix on failure. By default,
@@ -1308,8 +1312,6 @@ def do_install(self,
13081312
even with exceptions.
13091313
install_source (bool): By default, source is not installed, but
13101314
for debugging it might be useful to keep it around.
1311-
install_deps (bool): Install dependencies before installing this
1312-
package
13131315
skip_patch (bool): Skip patch stage of build if True.
13141316
verbose (bool): Display verbose build output (by default,
13151317
suppresses it)
@@ -1319,11 +1321,13 @@ def do_install(self,
13191321
explicit (bool): True if package was explicitly installed, False
13201322
if package was implicitly installed (as a dependency).
13211323
dirty (bool): Don't clean the build environment before installing.
1322-
force (bool): Install again, even if already installed.
1324+
1325+
Raises:
1326+
ValueError: if the ``self.spec`` is not concrete
13231327
"""
13241328
if not self.spec.concrete:
1325-
raise ValueError("Can only install concrete packages: %s."
1326-
% self.spec.name)
1329+
msg = "Can only install concrete packages: {0}."
1330+
raise ValueError(msg.format(self.spec.name))
13271331

13281332
# For external packages the workflow is simplified, and basically
13291333
# consists in module file generation and registration in the DB
@@ -1351,25 +1355,6 @@ def do_install(self,
13511355

13521356
return self._update_explicit_entry_in_db(rec, explicit)
13531357

1354-
self._do_install_pop_kwargs(kwargs)
1355-
1356-
# First, install dependencies recursively.
1357-
if install_deps:
1358-
tty.debug('Installing {0} dependencies'.format(self.name))
1359-
for dep in self.spec.traverse(order='post', root=False):
1360-
dep.package.do_install(
1361-
install_deps=False,
1362-
explicit=False,
1363-
keep_prefix=keep_prefix,
1364-
keep_stage=keep_stage,
1365-
install_source=install_source,
1366-
fake=fake,
1367-
skip_patch=skip_patch,
1368-
verbose=verbose,
1369-
make_jobs=make_jobs,
1370-
dirty=dirty,
1371-
**kwargs)
1372-
13731358
tty.msg(colorize('@*{Installing} @*g{%s}' % self.name))
13741359

13751360
if kwargs.get('use_cache', False):
@@ -1542,19 +1527,6 @@ def check_for_unfinished_installation(
15421527

15431528
return partial
15441529

1545-
def _do_install_pop_kwargs(self, kwargs):
1546-
"""Pops kwargs from do_install before starting the installation
1547-
1548-
Args:
1549-
kwargs:
1550-
'stop_at': last installation phase to be executed (or None)
1551-
1552-
"""
1553-
self.last_phase = kwargs.pop('stop_at', None)
1554-
if self.last_phase is not None and self.last_phase not in self.phases:
1555-
tty.die('\'{0}\' is not an allowed phase for package {1}'
1556-
.format(self.last_phase, self.name))
1557-
15581530
def log(self):
15591531
# Copy provenance into the install directory on success
15601532
log_install_path = spack.store.layout.build_log_path(self.spec)
@@ -1570,8 +1542,8 @@ def log(self):
15701542
# FIXME : this potentially catches too many things...
15711543
pass
15721544

1573-
install(self.log_path, log_install_path)
1574-
install(self.env_path, env_install_path)
1545+
filesystem.install(self.log_path, log_install_path)
1546+
filesystem.install(self.env_path, env_install_path)
15751547
dump_packages(self.spec, packages_dir)
15761548

15771549
def sanity_check_prefix(self):
@@ -2083,6 +2055,51 @@ class Package(PackageBase):
20832055
run_after('install')(PackageBase.sanity_check_prefix)
20842056

20852057

2058+
def install(spec, what=('root', 'dependencies'), stop_at=None, **kwargs):
2059+
"""Install a concretized spec and / or its dependencies.
2060+
2061+
This function permits to install either the entire DAG, only the
2062+
dependencies or only the root package. In any case the root package
2063+
will be installed as 'explicit', while the children as 'non-explicit'.
2064+
2065+
The keyword arguments are forwarded directly to
2066+
:py:meth:`PackageBase.do_install` during the installation of the
2067+
various nodes.
2068+
2069+
Args:
2070+
spec (Spec): spec to be installed. Must be concrete.
2071+
what (tuple of str): specifies what needs to be installed. If 'root'
2072+
is in the argument, then the root package of the spec will be
2073+
installed. If 'dependencies' is in the argument the child nodes
2074+
of the DAG will be installed. By default it installs both.
2075+
stop_at (str or None): if specified, stops the installation at the
2076+
given phase. Valid only for the root package.
2077+
**kwargs: keyword arguments to be forwarded to
2078+
:py:meth:`PackageBase.do_install`.
2079+
"""
2080+
# Check if we should stop early during the installation of the root spec
2081+
pkg = spec.package
2082+
pkg.last_phase = stop_at
2083+
2084+
if pkg.last_phase is not None and pkg.last_phase not in pkg.phases:
2085+
msg = '"{0}" is not an allowed phase for package {1}'
2086+
raise ValueError(msg.format(pkg.last_phase, pkg.name))
2087+
2088+
# Remove the keyword explicit if present: the root spec will be marked
2089+
# explicit, all the child specs will be non-explicit
2090+
kwargs.pop('explicit', None)
2091+
2092+
# Install the dependencies if required to, traversing the DAG
2093+
# in post order and omitting root
2094+
if 'dependencies' in what:
2095+
tty.debug('Installing {0} dependencies'.format(spec.name))
2096+
for dep in spec.traverse(order='post', root=False):
2097+
dep.package.do_install(explicit=False, **kwargs)
2098+
2099+
if 'root' in what:
2100+
spec.package.do_install(explicit=True, **kwargs)
2101+
2102+
20862103
def install_dependency_symlinks(pkg, spec, prefix):
20872104
"""Execute a dummy install and flatten dependencies"""
20882105
flatten_dependencies(spec, prefix)

lib/spack/spack/relocate.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import re
2929
import spack
3030
import spack.cmd
31+
import spack.package
3132
from spack.util.executable import Executable, ProcessError
3233
from llnl.util.filesystem import filter_file
3334
import llnl.util.tty as tty
@@ -42,10 +43,9 @@ def get_patchelf():
4243
# as we may need patchelf, find out where it is
4344
if platform.system() == 'Darwin':
4445
return None
45-
patchelf_spec = spack.cmd.parse_specs("patchelf", concretize=True)[0]
46-
patchelf = spack.repo.get(patchelf_spec)
46+
patchelf = spack.cmd.parse_specs("patchelf", concretize=True)[0]
4747
if not patchelf.installed:
48-
patchelf.do_install()
48+
spack.package.install(patchelf)
4949
patchelf_executable = os.path.join(patchelf.prefix.bin, "patchelf")
5050
return patchelf_executable
5151

lib/spack/spack/test/cmd/install.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
import spack
3434
import spack.cmd.install
35+
import spack.package
3536
from spack.spec import Spec
3637
from spack.main import SpackCommand, SpackCommandError
3738

@@ -139,8 +140,7 @@ def test_package_output(tmpdir, capsys, install_mockery, mock_fetch):
139140
# logging. TODO: see whether we can get multiple log_outputs to work
140141
# when nested AND in pytest
141142
spec = Spec('printing-package').concretized()
142-
pkg = spec.package
143-
pkg.do_install(verbose=True)
143+
spack.package.install(spec, verbose=True)
144144

145145
log_file = os.path.join(spec.prefix, '.spack', 'build.out')
146146
with open(log_file) as f:

lib/spack/spack/test/conftest.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,12 @@
4040
import spack.database
4141
import spack.directory_layout
4242
import spack.platforms.test
43+
import spack.package
4344
import spack.repository
4445
import spack.stage
4546
import spack.util.executable
4647
import spack.util.pattern
4748
from spack.dependency import Dependency
48-
from spack.package import PackageBase
4949
from spack.fetch_strategy import FetchStrategyComposite, URLFetchStrategy
5050
from spack.fetch_strategy import FetchError
5151
from spack.spec import Spec
@@ -306,8 +306,7 @@ def database(tmpdir_factory, builtin_mock, config):
306306
def _install(spec):
307307
s = spack.spec.Spec(spec)
308308
s.concretize()
309-
pkg = spack.repo.get(s)
310-
pkg.do_install(fake=True)
309+
spack.package.install(s, fake=True)
311310

312311
def _uninstall(spec):
313312
spec.package.do_uninstall(spec)
@@ -393,10 +392,10 @@ def mock_fetch(mock_archive):
393392
def fake_fn(self):
394393
return fetcher
395394

396-
orig_fn = PackageBase.fetcher
397-
PackageBase.fetcher = fake_fn
395+
orig_fn = spack.package.PackageBase.fetcher
396+
spack.package.PackageBase.fetcher = fake_fn
398397
yield
399-
PackageBase.fetcher = orig_fn
398+
spack.package.PackageBase.fetcher = orig_fn
400399

401400

402401
##########

lib/spack/spack/test/database.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
from llnl.util.tty.colify import colify
3434

3535
import spack
36+
import spack.package
3637
import spack.store
3738
from spack.test.conftest import MockPackageMultiRepo
3839
from spack.util.executable import Executable
@@ -108,8 +109,7 @@ def _check_db_sanity(install_db):
108109
def _mock_install(spec):
109110
s = spack.spec.Spec(spec)
110111
s.concretize()
111-
pkg = spack.repo.get(s)
112-
pkg.do_install(fake=True)
112+
spack.package.install(s, fake=True)
113113

114114

115115
def _mock_remove(spec):
@@ -417,7 +417,7 @@ def test_external_entries_in_db(database):
417417
assert rec.spec.external_module is None
418418
assert rec.explicit is False
419419

420-
rec.spec.package.do_install(fake=True, explicit=True)
420+
spack.package.install(rec.spec, fake=True)
421421
rec = install_db.get_record('externaltool')
422422
assert rec.spec.external_path == '/path/to/external_tool'
423423
assert rec.spec.external_module is None

0 commit comments

Comments
 (0)