Skip to content

Injection of Modules over Checks #295

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 40 commits into
base: v5
Choose a base branch
from
Open

Injection of Modules over Checks #295

wants to merge 40 commits into from

Conversation

CodeGat
Copy link
Member

@CodeGat CodeGat commented Jul 9, 2025

Closes #169
References #276

Background

Having to update versions of packages in multiple places (spack.packages.*.require[0] and spack.modules.tcl.default.projections mostly) has long been a source of errors and annoyance for users.

Rather than checking user manifests and reporting errors, we will now inject the relevant projections and includes into the manifest, and not override existing user projections.

As a little bonus, I've also added a module version of the prerelease modifications that are done in CD, too, so it's independently testable.

The PR

  • Add modules injection module
  • Add requirements for injection modules
  • Add tests for injection projection module, and inputs/requirements
  • Add prerelease injection module
  • Add tests for prerelease injection module
  • deploy-2-start: Run injection.modules and injection.prerelease modules
  • deploy-1-setup: Remove projection checks

Testing

Python Unit Tests

I've added tests for all the important functions in the scripts.spack_manifest.injection.modules and scripts.spack_manifest.injection.prerelease scripts.

These can be invoked with python3 -m pytest to run the full suite of build-cd tests, or python3 -m pytest tests/scripts/spack_manifest for just the new ones.

Local Module Runthrough

Can be done via the following commands. They will be most representative if the projections section is removed, or using your own manifests.

# For testing projection injection into the manifest
python3 -m scripts.spack_manifest.injection.modules --manifest tests/scripts/spack_manifest/injection/inputs/spack.yaml --root-spec access-om2 --packages "mom5 cice5 oasis3-mct libaccessom2"`

# For testing prerelease modifications to the manifest
python3 -m scripts.spack_manifest.injection.prerelease --manifest tests/scripts/spack_manifest/injection/inputs/prerelease.spack.yaml --root-spec access-om2 --version pr12-12

E2E Workflow Tests

For E2E tests, I used an ephemeral branch 169-inject-projections_TEST that used the same refs for all intra-pipeline workflow calls, and tested in on ACCESS-TEST. See ACCESS-NRI/ACCESS-TEST#48.

Inject Projections As Prerelease

In this run, you can see the original manifest both after projections are injected (done both in Prerelease and Release) and then after prerelease modifications are made. The injections are also visible in the artifacts generated by the run, see at the bottom of the summary in the metadata artifact - https://github.com/ACCESS-NRI/ACCESS-TEST/actions/runs/16161252034?pr=48

Inject Projections Where They Already Exist

I also tested injecting projections where they were already defined, and it succeeded - see ACCESS-NRI/ACCESS-ESM1.5#38. This could be for the case where users haven't got around to deleting the projections section.

@CodeGat CodeGat added type:enhancement Improvements to existing features priority:high version:MINOR Doesn't require update to Model Deployment Repositories for:v5 Applies to `v5` labels Jul 9, 2025
@CodeGat CodeGat self-assigned this Jul 9, 2025
@github-project-automation github-project-automation bot moved this to New Issues 🌅 in Model Release Jul 9, 2025
@aidanheerdegen
Copy link
Member

In the test run the pre-release injection results in the following change to projections:

         projections:
-          access-test: '{name}/2025.06.001'
+          access-test: '{name}/pr48-7'
           access-test-component: '{name}/main-{hash:7}'

So only the main spec is modified. Is that how we're currently doing it? Do we not get module namespace collisions because the components have their spack hash included, so if they're the same then it's all cool? What about when we delete the environment, do the module files get cleaned up too?

I'm just wondering if we're doing this, maybe we should add the PR namespace to the component as well, e.g.

access-test-component: '{name}/pr48-7/main-{hash:7}'

Then we could cleanly remove all module files within the pr48-* directories.

@CodeGat
Copy link
Member Author

CodeGat commented Jul 9, 2025

Yep, that's how we're currently doing it. And yeah, we don't get collisions because of the spack hash.

When we delete the environment, the modulefiles are not cleaned up, unfortunately. See #155 - still finding a way to clean them up. I've tried a bunch of spacks ways to remove the modulefiles (spack module tcl rm, etc) but it seems to mess with existing modulefiles as well. Maybe doing a spack -e ENV module tcl rm?

And yes it could be good to put the PR namespace into the components - I'll investigate that. If we can do an environment-aware module tcl rm we may not need to, though.

@aidanheerdegen
Copy link
Member

And yes it could be good to put the PR namespace into the components - I'll investigate that.

👍

If we can do an environment-aware module tcl rm we may not need to, though.

True. As long as spack knows which environments are using a tcl module file. If there are multiple, and it doesn't know this, then removing for one environment could break another.

The nice thing about using the name-space is that it would avoid above, and if you can't get spack to do what you want, then it's simple to remove them manually.

@CodeGat
Copy link
Member Author

CodeGat commented Jul 10, 2025

Should this forced namespacing be the case for projections supplied by the user?

If so, this goes against the rationale of not overriding user projections.
And if not, we don't really have a way of knowing what projections are specified by the user in the prerelease module (since projection injection happens first). .

@aidanheerdegen
Copy link
Member

Should this forced namespacing be the case for projections supplied by the user?

I guess not.

If so, this goes against the rationale of not overriding user projections. And if not, we don't really have a way of knowing what projections are specified by the user in the prerelease module (since projection injection happens first). .

It's going to be vanishingly rare for people to bother specifying their own projections. So in that case we live with there being some funky modules sitting around I suppose. Can always clean up anything older than, say, 12 months.

Copy link
Member

@aidanheerdegen aidanheerdegen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll submit this review (half finished) now so I can pull in the most recent commits and I don't get confused between what I've done already and what is new.

@CodeGat CodeGat requested a review from aidanheerdegen July 14, 2025 05:15
@CodeGat CodeGat changed the title Injection Projections over Checks Injection of Modules over Checks Jul 14, 2025
Copy link
Member

@aidanheerdegen aidanheerdegen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks really good, thanks. I do have some suggestions still though.

@CodeGat CodeGat requested a review from aidanheerdegen July 17, 2025 01:53
Copy link
Member

@aidanheerdegen aidanheerdegen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have reviewed some ... still some more to look through.

Edit: not sure actually, so I'll leave it as-is and let me know if you need another review.

Comment on lines 75 to 97
# def get_spack_version(self) -> str:
# pass

## Get variant information

# def get_variants(self) -> list[str]:
# pass

## Get compiler information

# def get_compiler(self) -> str:
# pass

# def get_compiler_name(self) -> str:
# pass

# def get_compiler_version(self) -> str:
# pass

## Get architecture information

# def get_arch(self) -> str:
# pass
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intention to provide these methods at a later date?

If so, would it be better to not have them commented, but instead return a NotImplemented exception?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9ac842d

# def get_full_version(self) -> str:
# pass

def get_ref(self) -> str:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have a comment with an example or two of specs it would match against

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also done in 9ac842d

if requirements == []:
raise NoSectionComponentError(f"Package component 'version' could not be extracted from the package requirements string for '{name}'.")

match = re.match(r"@(?:git\.)?([^+~=% ]+)", requirements[0])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have a comment illustrating the uses cases and the expected match.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 9ac842d

Comment on lines 55 to 56
# defined_projections: dict[str, str] = _get_defined_projections(manifest)
# defined_projections_set: set[str] = set(defined_projections.keys())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the intention is to remove the commented code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}
}

expected = ["root-spec", "package2", "package3"]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above, could just compare directly to manifest['spack']['modules']['default']['tcl']['include'], or defined expected first and use that to populate manifest.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, done via 804e577

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for:v5 Applies to `v5` priority:high type:enhancement Improvements to existing features version:MINOR Doesn't require update to Model Deployment Repositories
Projects
Status: Review 👏
Development

Successfully merging this pull request may close these issues.

Inject Values for spack.modules.default.tcl.projections
2 participants