Skip to content

Conversation

@mbg
Copy link
Member

@mbg mbg commented Sep 17, 2025

This PR modifies our implementation of dependency caching for C# to allow non-lock files to be used when computing a hash for the cache key.

The hash that's included in the cache key is derived from files which contain information about the project's dependency. The idea being that the hash changes only if the dependencies change. Currently, we only use packages.lock.json and paket.lock files for this. These files contain exact dependency information (including the exact versions that were resolved by the respective package manager). The resulting hashes we compute for the cache keys should therefore only change if the dependencies (including versions) change. This is the desired behaviour.

However, relatively few C# projects use lock files and fewer yet have them checked-in to the repository. This means that most C# projects do not benefit from our implementation of dependency caching at the moment.

This PR modifies the behaviour of our dependency caching implementation for C# as follows:

  • As before, we search for packages.lock.json or paket.lock files. If they exist, we use them to compute a hash for the cache key.
  • If no such files exist, and the new CsharpNewCacheKey feature is enabled (via FF or environment variable), we search for additional files that typically contain dependency information for C# projects. If they exist, we use them to compute the hash for the cache key.

Using the additional files is less precise, because they may not contain exact version information (only version ranges or no versions at all) and they contain information unrelated to dependencies. This means that the hash we compute can change, even if the dependencies don't change. However, dependency caching will benefit more C# projects.

This PR is best reviewed commit-by-commit. The first few commits just add the new Feature and refactor the code a bit to facilitate the changes.

Questions for reviewers:

  • Right now, disabling the Feature wouldn't avoid restoring caches that were produced with the Feature enabled. We likely want to do something similar as for Add feature flag to roll out JAR minimization in the Java extractor #3107 that marks caches produced with this Feature on.
  • Should we use this opportunity to also extend the list of paths that are included in the cache for C#? The setup-dotnet Action considers more paths and computes them dynamically. Now that we have getDependencyPaths as a function, it would be easy to do this.
  • If so, should that be gated behind the same FF?
  • Are there any additional files we should include for C# hashes?

Risk assessment

For internal use only. Please select the risk level of this change:

  • Low risk: Changes are fully under feature flags, or have been fully tested and validated in pre-production environments and are highly observable, or are documentation or test only.

Merge / deployment checklist

  • Confirm this change is backwards compatible with existing workflows.
  • Consider adding a changelog entry for this change.
  • Confirm the readme and docs have been updated if necessary.

@mbg mbg requested a review from a team as a code owner September 17, 2025 14:53
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR enhances C# dependency caching support by allowing non-lock files to be used when computing cache keys. Currently, only packages.lock.json and paket.lock files are used for cache key computation, which limits caching benefits to projects that have these lock files checked in. The new implementation maintains backward compatibility while expanding support to more C# projects through an optional feature flag.

Key changes:

  • Adds a new CsharpNewCacheKey feature flag with environment variable support
  • Refactors cache configuration from static data structures to dynamic functions
  • Implements fallback logic to use .csproj, packages.config, and nuget.config files when lock files are unavailable

Reviewed Changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/feature-flags.ts Adds the new CsharpNewCacheKey feature flag configuration
src/dependency-caching.ts Core refactoring to support dynamic hash pattern generation and C# fallback logic
src/init-action.ts Updates function calls to pass CodeQL and features parameters
src/analyze-action.ts Updates function calls to pass CodeQL and features parameters
lib/* Generated JavaScript files reflecting the TypeScript changes

Copy link
Contributor

@henrymercer henrymercer left a comment

Choose a reason for hiding this comment

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

Looks reasonable from an Action perspective

Copy link

@michaelnebel michaelnebel left a comment

Choose a reason for hiding this comment

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

Looks plausible to me!

@mbg mbg force-pushed the mbg/csharp/new-cache-key-calculation branch from 7f81d73 to 2680455 Compare November 5, 2025 17:34
@github-actions github-actions bot added the size/L May be hard to review label Nov 5, 2025
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 19 out of 19 changed files in this pull request and generated 2 comments.

* @param codeql The CodeQL instance to use.
* @param features Information about which FFs are enabled.
* @param language The language being analyzed.
* @param cacheConfig The cache configuration for the language.
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

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

The JSDoc parameter description for cacheConfig is incorrect. This parameter has been removed from the function signature but the documentation still references it. The function now receives patterns instead.

Suggested change
* @param cacheConfig The cache configuration for the language.
* @param patterns The file patterns to hash for the cache key.

Copilot uses AI. Check for mistakes.

if (patterns === undefined) {
logger.info(
`Skipping download of dependency cache for ${language} as we cannot calculate a hash for the cache key.`,
Copy link

Copilot AI Nov 5, 2025

Choose a reason for hiding this comment

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

The log message says 'Skipping download' but the function checkHashPatterns is also used in the upload path. Consider making the message more generic or having separate messages for download vs upload contexts.

Suggested change
`Skipping download of dependency cache for ${language} as we cannot calculate a hash for the cache key.`,
`Skipping dependency cache for ${language} as we cannot calculate a hash for the cache key.`,

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems valid, though "Skipping dependency caching" seems more natural.

Copy link

@michaelnebel michaelnebel left a comment

Choose a reason for hiding this comment

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

The new pattern for deciding which C# files to use in the cache hash LGTM!

Should we use this opportunity to also extend the list of paths that are included in the cache for C#? The setup-dotnet Action considers more paths and computes them dynamically. Now that we have getDependencyPaths as a function, it would be easy to do this.

Does this mean that we are "only" including "{working_dir}/.nuget/packages" in the cache?

The dependency manager for C# use the "scratch dir" for storing downloaded packages.

Copy link
Contributor

@nickrolfe nickrolfe left a comment

Choose a reason for hiding this comment

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

I only reviewed the parts that generalise the mechanism to add a feature-flag-specific prefix to the cache key, but that looks good.

Copy link
Contributor

@henrymercer henrymercer left a comment

Choose a reason for hiding this comment

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

This generally looks good. The only blocking comment from me is the naming of the cache entries.

For PRs like this in the future, consider splitting up into a core functionality part (e.g. here, handling the addition of feature flags to the cache key), and a language-specific part (e.g. here, changing the cache key for C#).

Comment on lines +130 to +133
// If we get to this point, the `basePatterns` didn't find any files,
// and `Feature.CsharpNewCacheKey` is either not enabled or we didn't
// find any files using those patterns either.
return undefined;
Copy link
Contributor

Choose a reason for hiding this comment

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

The last bit isn't true since we'll return the result of makePatternCheck, even if it's undefined. It might be clearer to have a helper function that checks whether the patterns match anything, and use that here instead.


if (patterns === undefined) {
logger.info(
`Skipping download of dependency cache for ${language} as we cannot calculate a hash for the cache key.`,
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems valid, though "Skipping dependency caching" seems more natural.


return `${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
// Assemble the cache key.
return `${featurePrefix}${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
Copy link
Contributor

Choose a reason for hiding this comment

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

If users list cache entries using the API, I think it'd be clearer to them what those cache entries are for if we keep codeql-dependencies- at the front.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/L May be hard to review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants