Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions samcli/lib/build/dependency_hash_generator.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
"""Utility Class for Getting Function or Layer Manifest Dependency Hashes"""

import hashlib
import pathlib
from typing import Any, Optional

from samcli.lib.build.workflow_config import get_workflow_config
from samcli.lib.utils.hash import file_checksum


# Mapping of dependency managers to their lock file names
LOCK_FILE_MAPPING = {
"npm": "package-lock.json",
"npm-esbuild": "package-lock.json",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we not need to add yarn.lock and pnpm-lock.yaml?

"bundler": "Gemfile.lock",
"gradle": "gradle.lockfile",
"cli-package": "packages.lock.json",
"modules": "go.sum",
"cargo": "Cargo.lock",
"uv": "uv.lock",
"poetry": "poetry.lock",
}


# TODO Expand this class to hash specific sections of the manifest
class DependencyHashGenerator:
_code_uri: str
Expand Down Expand Up @@ -50,15 +65,17 @@ def __init__(
self._hash = None

def _calculate_dependency_hash(self) -> Optional[str]:
"""Calculate the manifest file hash
"""Calculate the manifest file hash, including lock file if applicable

Returns
-------
Optional[str]
Returns manifest hash. If manifest does not exist or not supported, None will be returned.
Returns combined hash of manifest and lock file (if present).
If manifest does not exist or not supported, None will be returned.
"""
if self._manifest_path_override:
manifest_file = self._manifest_path_override
config = None
Copy link
Copy Markdown
Contributor

@seshubaws seshubaws Mar 17, 2026

Choose a reason for hiding this comment

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

why are we not wanting to add the config to the hash if there is a different manifest path? Can the lock file not still be resolved relative to the override path's directory?

else:
config = get_workflow_config(self._runtime, self._code_dir, self._base_dir)
manifest_file = config.manifest_name
Expand All @@ -70,7 +87,24 @@ def _calculate_dependency_hash(self) -> Optional[str]:
if not manifest_path.is_file():
return None

return file_checksum(str(manifest_path), hash_generator=self._hash_generator)
manifest_hash = file_checksum(str(manifest_path), hash_generator=self._hash_generator)

# Check if there's a lock file for this dependency manager
if config and config.dependency_manager in LOCK_FILE_MAPPING:
lock_file_name = LOCK_FILE_MAPPING[config.dependency_manager]
lock_file_path = pathlib.Path(self._code_dir, lock_file_name).resolve()

# If lock file exists, combine hashes
if lock_file_path.is_file():
lock_file_hash = file_checksum(str(lock_file_path), hash_generator=self._hash_generator)

# Combine both hashes into a single hash
combined = f"{manifest_hash}:{lock_file_hash}"
hasher = self._hash_generator() if self._hash_generator else hashlib.md5()
hasher.update(combined.encode("utf-8"))
return hasher.hexdigest()

return manifest_hash

@property
def hash(self) -> Optional[str]:
Expand Down
Loading