3636from spack .spec import Spec
3737from spack .spec_list import SpecList , InvalidSpecConstraintError
3838from spack .variant import UnknownVariantError
39+ import spack .util .lock as lk
3940
4041#: environment variable used to indicate the active environment
4142spack_env_var = 'SPACK_ENV'
@@ -557,6 +558,9 @@ def __init__(self, path, init_file=None, with_view=None):
557558 path to the view.
558559 """
559560 self .path = os .path .abspath (path )
561+
562+ self .txlock = lk .Lock (self ._transaction_lock_path )
563+
560564 # This attribute will be set properly from configuration
561565 # during concretization
562566 self .concretization = None
@@ -571,26 +575,7 @@ def __init__(self, path, init_file=None, with_view=None):
571575 else :
572576 self ._read_manifest (f , raw_yaml = default_manifest_yaml )
573577 else :
574- default_manifest = not os .path .exists (self .manifest_path )
575- if default_manifest :
576- # No manifest, use default yaml
577- self ._read_manifest (default_manifest_yaml )
578- else :
579- with open (self .manifest_path ) as f :
580- self ._read_manifest (f )
581-
582- if os .path .exists (self .lock_path ):
583- with open (self .lock_path ) as f :
584- read_lock_version = self ._read_lockfile (f )
585- if default_manifest :
586- # No manifest, set user specs from lockfile
587- self ._set_user_specs_from_lockfile ()
588-
589- if read_lock_version == 1 :
590- tty .debug (
591- "Storing backup of old lockfile {0} at {1}" .format (
592- self .lock_path , self ._lock_backup_v1_path ))
593- shutil .copy (self .lock_path , self ._lock_backup_v1_path )
578+ self ._read ()
594579
595580 if with_view is False :
596581 self .views = {}
@@ -602,6 +587,42 @@ def __init__(self, path, init_file=None, with_view=None):
602587 # If with_view is None, then defer to the view settings determined by
603588 # the manifest file
604589
590+ def _re_read (self ):
591+ """Reinitialize the environment object if it has been written (this
592+ may not be true if the environment was just created in this running
593+ instance of Spack)."""
594+ if not os .path .exists (self .manifest_path ):
595+ return
596+
597+ self .clear ()
598+ self ._read ()
599+
600+ def _read (self ):
601+ default_manifest = not os .path .exists (self .manifest_path )
602+ if default_manifest :
603+ # No manifest, use default yaml
604+ self ._read_manifest (default_manifest_yaml )
605+ else :
606+ with open (self .manifest_path ) as f :
607+ self ._read_manifest (f )
608+
609+ if os .path .exists (self .lock_path ):
610+ with open (self .lock_path ) as f :
611+ read_lock_version = self ._read_lockfile (f )
612+ if default_manifest :
613+ # No manifest, set user specs from lockfile
614+ self ._set_user_specs_from_lockfile ()
615+
616+ if read_lock_version == 1 :
617+ tty .debug (
618+ "Storing backup of old lockfile {0} at {1}" .format (
619+ self .lock_path , self ._lock_backup_v1_path ))
620+ shutil .copy (self .lock_path , self ._lock_backup_v1_path )
621+
622+ def write_transaction (self ):
623+ """Get a write lock context manager for use in a `with` block."""
624+ return lk .WriteTransaction (self .txlock , acquire = self ._re_read )
625+
605626 def _read_manifest (self , f , raw_yaml = None ):
606627 """Read manifest file and set up user specs."""
607628 if raw_yaml :
@@ -694,6 +715,13 @@ def manifest_path(self):
694715 """Path to spack.yaml file in this environment."""
695716 return os .path .join (self .path , manifest_name )
696717
718+ @property
719+ def _transaction_lock_path (self ):
720+ """The location of the lock file used to synchronize multiple
721+ processes updating the same environment.
722+ """
723+ return os .path .join (self .path , 'transaction_lock' )
724+
697725 @property
698726 def lock_path (self ):
699727 """Path to spack.lock file in this environment."""
@@ -986,11 +1014,18 @@ def _concretize_separately(self):
9861014 concretized_specs .append ((uspec , concrete ))
9871015 return concretized_specs
9881016
989- def install (self , user_spec , concrete_spec = None , ** install_args ):
990- """Install a single spec into an environment.
1017+ def concretize_and_add (self , user_spec , concrete_spec = None ):
1018+ """Concretize and add a single spec to the environment.
9911019
992- This will automatically concretize the single spec, but it won't
993- affect other as-yet unconcretized specs.
1020+ Concretize the provided ``user_spec`` and add it along with the
1021+ concretized result to the environment. If the given ``user_spec`` was
1022+ already present in the environment, this does not add a duplicate.
1023+ The concretized spec will be added unless the ``user_spec`` was
1024+ already present and an associated concrete spec was already present.
1025+
1026+ Args:
1027+ concrete_spec: if provided, then it is assumed that it is the
1028+ result of concretizing the provided ``user_spec``
9941029 """
9951030 if self .concretization == 'together' :
9961031 msg = 'cannot install a single spec in an environment that is ' \
@@ -1001,37 +1036,21 @@ def install(self, user_spec, concrete_spec=None, **install_args):
10011036
10021037 spec = Spec (user_spec )
10031038
1004- with spack .store .db .read_transaction ():
1005- if self .add (spec ):
1006- concrete = concrete_spec or spec .concretized ()
1039+ if self .add (spec ):
1040+ concrete = concrete_spec or spec .concretized ()
1041+ self ._add_concrete_spec (spec , concrete )
1042+ else :
1043+ # spec might be in the user_specs, but not installed.
1044+ # TODO: Redo name-based comparison for old style envs
1045+ spec = next (
1046+ s for s in self .user_specs if s .satisfies (user_spec )
1047+ )
1048+ concrete = self .specs_by_hash .get (spec .build_hash ())
1049+ if not concrete :
1050+ concrete = spec .concretized ()
10071051 self ._add_concrete_spec (spec , concrete )
1008- else :
1009- # spec might be in the user_specs, but not installed.
1010- # TODO: Redo name-based comparison for old style envs
1011- spec = next (
1012- s for s in self .user_specs if s .satisfies (user_spec )
1013- )
1014- concrete = self .specs_by_hash .get (spec .build_hash ())
1015- if not concrete :
1016- concrete = spec .concretized ()
1017- self ._add_concrete_spec (spec , concrete )
10181052
1019- self ._install (concrete , ** install_args )
1020-
1021- def _install (self , spec , ** install_args ):
1022- spec .package .do_install (** install_args )
1023-
1024- # Make sure log directory exists
1025- log_path = self .log_path
1026- fs .mkdirp (log_path )
1027-
1028- with fs .working_dir (self .path ):
1029- # Link the resulting log file into logs dir
1030- build_log_link = os .path .join (
1031- log_path , '%s-%s.log' % (spec .name , spec .dag_hash (7 )))
1032- if os .path .lexists (build_log_link ):
1033- os .remove (build_log_link )
1034- os .symlink (spec .package .build_log_path , build_log_link )
1053+ return concrete
10351054
10361055 @property
10371056 def default_view (self ):
@@ -1131,32 +1150,61 @@ def _add_concrete_spec(self, spec, concrete, new=True):
11311150 self .concretized_order .append (h )
11321151 self .specs_by_hash [h ] = concrete
11331152
1153+ def install (self , user_spec , concrete_spec = None , ** install_args ):
1154+ """Install a single spec into an environment.
1155+
1156+ This will automatically concretize the single spec, but it won't
1157+ affect other as-yet unconcretized specs.
1158+ """
1159+ concrete = self .concretize_and_add (user_spec , concrete_spec )
1160+
1161+ self ._install (concrete , ** install_args )
1162+
1163+ def _install (self , spec , ** install_args ):
1164+ # "spec" must be concrete
1165+ spec .package .do_install (** install_args )
1166+
1167+ if not spec .external :
1168+ # Make sure log directory exists
1169+ log_path = self .log_path
1170+ fs .mkdirp (log_path )
1171+
1172+ with fs .working_dir (self .path ):
1173+ # Link the resulting log file into logs dir
1174+ build_log_link = os .path .join (
1175+ log_path , '%s-%s.log' % (spec .name , spec .dag_hash (7 )))
1176+ if os .path .lexists (build_log_link ):
1177+ os .remove (build_log_link )
1178+ os .symlink (spec .package .build_log_path , build_log_link )
1179+
11341180 def install_all (self , args = None ):
11351181 """Install all concretized specs in an environment.
11361182
11371183 Note: this does not regenerate the views for the environment;
11381184 that needs to be done separately with a call to write().
11391185
11401186 """
1187+
1188+ # If "spack install" is invoked repeatedly for a large environment
1189+ # where all specs are already installed, the operation can take
1190+ # a large amount of time due to repeatedly acquiring and releasing
1191+ # locks, this does an initial check across all specs within a single
1192+ # DB read transaction to reduce time spent in this case.
1193+ uninstalled_specs = []
11411194 with spack .store .db .read_transaction ():
11421195 for concretized_hash in self .concretized_order :
11431196 spec = self .specs_by_hash [concretized_hash ]
1197+ if not spec .package .installed :
1198+ uninstalled_specs .append (spec )
1199+
1200+ for spec in uninstalled_specs :
1201+ # Parse cli arguments and construct a dictionary
1202+ # that will be passed to Package.do_install API
1203+ kwargs = dict ()
1204+ if args :
1205+ spack .cmd .install .update_kwargs_from_args (args , kwargs )
11441206
1145- # Parse cli arguments and construct a dictionary
1146- # that will be passed to Package.do_install API
1147- kwargs = dict ()
1148- if args :
1149- spack .cmd .install .update_kwargs_from_args (args , kwargs )
1150-
1151- self ._install (spec , ** kwargs )
1152-
1153- if not spec .external :
1154- # Link the resulting log file into logs dir
1155- log_name = '%s-%s' % (spec .name , spec .dag_hash (7 ))
1156- build_log_link = os .path .join (self .log_path , log_name )
1157- if os .path .lexists (build_log_link ):
1158- os .remove (build_log_link )
1159- os .symlink (spec .package .build_log_path , build_log_link )
1207+ self ._install (spec , ** kwargs )
11601208
11611209 def all_specs_by_hash (self ):
11621210 """Map of hashes to spec for all specs in this environment."""
0 commit comments