Add lockfiles
How to add lockfiles and the generate-lockfiles
goal.
Lockfiles are a way to pin to exact versions of dependencies, often including hashes to guarantee integrity between the version pinned and the version downloaded.
This guide will walk you through implementing lockfiles and hooking them into the generate-lockfiles
goal. It assumes that your language has a tool that supports generating and using lockfiles or that you have written code which does these.
1. Expose your lockfiles to Pants
Create subclasses of KnownUserResolveNamesRequest
to inform Pants about which resolves exist, and a subclass of RequestedUserResolveNames
for Pants to request those resolves later. Implement the resolve-finding logic in a Rule from your subclass of KnownUserResolveNamesRequest
to KnownUserResolveNames
. Set KnownResolveNames.requested_resolve_names_cls
to your subclass of RequestedUserResolveNames
.
- pants-plugins/fortran/lockfiles.py
from pants.core.goals.generate_lockfiles import KnownUserResolveNamesRequest, RequestedUserResolveNames, KnownUserResolveNames
from pants.engine.rules import rule
from pants.engine.target import AllTargets
class KnownFortranResolveNamesRequest(KnownUserResolveNamesRequest):
pass
class RequestedFortranResolveNames(RequestedUserResolveNames):
pass
@rule
async def identify_user_resolves_from_fortran_files(
_: KnownFortranResolveNamesRequest,
all_targets: AllTargets,
) -> KnownUserResolveNames:
...
return KnownUserResolveNames(
...,
requested_resolve_names_cls=RequestedFortranResolveNames
)
2. Connect resolve names to requests to generate lockfiles
Create a subclass of GenerateLockfile
. Pants will use this to represent a lockfile to generate. Then create a rule from your subclass of RequestedUserResolveNames
to UserGenerateLockfiles
. Pants will use this rule to convert from a user's request to export a resolve by name into the information needed to export the resolve.
- pants-plugins/fortran/lockfiles.py
from dataclasses import dataclass
from pants.backend.fortran.target_types import FortranDeploymentTarget
from pants.core.goals.generate_lockfiles import GenerateLockfile, UserGenerateLockfiles
from pants.engine.rules import rule
@dataclass(frozen=True)
class GenerateFortranLockfile(GenerateLockfile):
target: FortranDeploymentTarget
@rule
async def setup_user_lockfile_requests(
requested: RequestedFortranResolveNames,
) -> UserGenerateLockfiles:
...
return UserGenerateLockfiles(
[
GenerateFortranLockfile(
...
)
]
)
3. Generate lockfiles
Create a rule from your subclass of GenerateLockfile
to GenerateLockfileResult
. This rule generates the lockfile. In the common case that you're running a process to generate this lockfile, you can use the Process.output_files
to gather those files from the execution sandbox.
- pants-plugins/fortran/lockfiles.py
from pants.backend.fortran.tool import FortranProcess
from pants.core.goals.generate_lockfiles import GenerateLockfileResult
from pants.engine.internals.selectors import Get
from pants.engine.process import ProcessResult
from pants.engine.rules import rule
@rule
async def generate_lockfile_from_sources(
request: GenerateFortranLockfile,
) -> GenerateLockfileResult:
...
result = await Get(
ProcessResult,
FortranProcess(...),
)
return GenerateLockfileResult(result.output_digest, request.resolve_name, request.lockfile_dest)
4. Register rules
At the bottom of the file, let Pants know what your rules and types do. Update your plugin's register.py
to tell Pants about them/
- pants-plugins/fortran/lockfiles.py
- pants-plugins/fortran/register.py
from pants.core.goals.generate_lockfiles import GenerateLockfile, KnownUserResolveNamesRequest, RequestedUserResolveNames
from pants.engine.rules import collect_rules
from pants.engine.unions import UnionRule
def rules():
return (
*collect_rules(),
UnionRule(GenerateLockfile, GenerateFortranLockfile),
UnionRule(KnownUserResolveNamesRequest, KnownFortranResolveNamesRequest),
UnionRule(RequestedUserResolveNames, RequestedFortranResolveNames),
)
from fortran import lockfiles
def rules():
return [
...,
*lockfiles.rules()
]
5. Use lockfiles for fetching dependencies
If you have a tool that supports lockfiles, the easiest way to get the lockfile to it is to simply use a glob to pull the file into a digest.
from pathlib import Path
from pants.engine.fs import PathGlobs
from pants.engine.internals.native_engine import Snapshot
from pants.engine.internals.selectors import Get
from pants.engine.rules import rule
@rule
async def init_fortran(request: FortranInitRequest) -> FortranInitResponse:
...
Get(Snapshot, PathGlobs([(Path(request.root_module.address.spec_path) / ".fortran.lock").as_posix()])),