Repository: spack/spack

Issue:
spack env update doesn't update environment definition file format
When an environment definition file has paths / modules but unfilled, `spack env update env-name` doesn't update the definition file to the new format introduced in https://github.com/spack/spack/pull/17804.

Example : 
```
(ipy3) [b289150@beboplogin1 PIRT]$ cat spack/var/spack/environments/pirt/spack.yaml
# This is a Spack Environment file.
#
# It describes a set of packages to be installed, along with
# configuration settings.
spack:
  packages:
    all:
      providers:
        mpi: [intel-parallel-studio]
        blas: [intel-parallel-studio]
        lapack: [intel-parallel-studio]
      compiler: [intel@19.1.2]
      buildable: true
      version: []
      target: []
      paths: {}
      modules: {}
  specs:
  - petsc@develop ~complex+debug+hdf5+mpi
  - fftw+mpi
  - hdf5@1.12.0 +mpi+szip+hl
  - boost cxxstd=11
  concretization: together
  view: true
(ipy3) [b289150@beboplogin1 PIRT]$
```

Attempt to update the above to the new format :
```
(ipy3) [b289150@bdwd-0024 PIRT]$ spack env update pirt && spack env activate pirt
==> No update needed for the environment "pirt"
==> Warning: the attribute "paths" in the "packages" section of the configuration has been deprecated [entry=CommentedMap()]
==> Warning: the attribute "modules" in the "packages" section of the configuration has been deprecated [entry=CommentedMap()]
==> Warning: the attribute "paths" in the "packages" section of the configuration has been deprecated [entry=CommentedMap()]
==> Warning: the attribute "modules" in the "packages" section of the configuration has been deprecated [entry=CommentedMap()]
(ipy3) [b289150@bdwd-0024 PIRT]$
```

@alalazo 



Discussion:

Resolving patch:
diff --git a/lib/spack/spack/schema/packages.py b/lib/spack/spack/schema/packages.py
--- a/lib/spack/spack/schema/packages.py
+++ b/lib/spack/spack/schema/packages.py
@@ -128,6 +128,14 @@ def update(data):
     changed = False
     for cfg_object in data.values():
         externals = []
+
+        # If we don't have these deprecated attributes, continue
+        if not any(x in cfg_object for x in ('paths', 'modules')):
+            continue
+
+        # If we arrive here we need to make some changes i.e.
+        # we need to remove and eventually convert some attributes
+        changed = True
         paths = cfg_object.pop('paths', {})
         for spec, prefix in paths.items():
             externals.append({
@@ -141,6 +149,6 @@ def update(data):
                 'modules': [str(module)]
             })
         if externals:
-            changed = True
             cfg_object['externals'] = externals
+
     return changed


Test patch:
diff --git a/lib/spack/spack/test/cmd/config.py b/lib/spack/spack/test/cmd/config.py
--- a/lib/spack/spack/test/cmd/config.py
+++ b/lib/spack/spack/test/cmd/config.py
@@ -551,6 +551,27 @@ def test_config_update_can_handle_comments(mutable_config):
     assert '# Another comment after the outdated section' in text
 
 
+@pytest.mark.regression('18050')
+def test_config_update_works_for_empty_paths(mutable_config):
+    # Create an outdated config file with empty "paths" and "modules"
+    scope = spack.config.default_modify_scope()
+    cfg_file = spack.config.config.get_config_filename(scope, 'packages')
+    with open(cfg_file, mode='w') as f:
+        f.write("""
+packages:
+  cmake:
+    paths: {}
+    modules: {}
+    buildable: False
+""")
+
+    # Try to update it, it should not raise errors
+    output = config('update', '-y', 'packages')
+
+    # This ensures that we updated the configuration
+    assert '[backup=' in output
+
+
 def check_update(data):
     """Check that the data from the packages_yaml_v015
     has been updated.