Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Release 0.11.0 (unreleased)
* Use CycloneDX schema version 1.6 (#542)
* Add security policy (#784)
* Add provenance / release attestation to pypi package (#784)
* Support multiple licenses per project (#788)
* Add evidence to sbom report (#788)

Release 0.10.0 (released 2025-03-12)
====================================
Expand Down
23 changes: 13 additions & 10 deletions dfetch/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import dfetch.project
from dfetch.commands.common import check_child_manifests
from dfetch.log import get_logger
from dfetch.manifest.manifest import Manifest
from dfetch.reporting.check.code_climate_reporter import CodeClimateReporter
from dfetch.reporting.check.jenkins_reporter import JenkinsReporter
from dfetch.reporting.check.reporter import CheckReporter
Expand Down Expand Up @@ -83,18 +84,18 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Perform the check."""
manifest, path = dfetch.manifest.manifest.get_manifest()
reporters = self._get_reporters(args, path)
manifest = dfetch.manifest.manifest.get_manifest()
reporters = self._get_reporters(args, manifest)

with in_directory(os.path.dirname(path)):
with in_directory(os.path.dirname(manifest.path)):
exceptions: List[str] = []
for project in manifest.selected_projects(args.projects):
with catch_runtime_exceptions(exceptions) as exceptions:
dfetch.project.make(project).check_for_update(reporters)

if not args.no_recommendations and os.path.isdir(project.destination):
with in_directory(project.destination):
check_child_manifests(manifest, project, path)
check_child_manifests(manifest, project)

for reporter in reporters:
reporter.dump_to_file()
Expand All @@ -103,21 +104,23 @@ def __call__(self, args: argparse.Namespace) -> None:
raise RuntimeError("\n".join(exceptions))

@staticmethod
def _get_reporters(args: argparse.Namespace, path: str) -> List[CheckReporter]:
def _get_reporters(
args: argparse.Namespace, manifest: Manifest
) -> List[CheckReporter]:
"""Get all reporters.

Args:
args (argparse.Namespace): Arguments given to the command line
path (str): Path to the manifest
manifest (Manifest): The manifest

Returns:
List[CheckReporter]: List of reporters that each provide a unique report
"""
reporters: List[CheckReporter] = [CheckStdoutReporter(path)]
reporters: List[CheckReporter] = [CheckStdoutReporter(manifest)]
if args.jenkins_json:
reporters += [JenkinsReporter(path, args.jenkins_json)]
reporters += [JenkinsReporter(manifest, args.jenkins_json)]
if args.sarif:
reporters += [SarifReporter(path, args.sarif)]
reporters += [SarifReporter(manifest, args.sarif)]
if args.code_climate:
reporters += [CodeClimateReporter(path, args.code_climate)]
reporters += [CodeClimateReporter(manifest, args.code_climate)]
return reporters
14 changes: 5 additions & 9 deletions dfetch/commands/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,14 @@
logger = get_logger(__name__)


def check_child_manifests(manifest: Manifest, project: ProjectEntry, path: str) -> None:
def check_child_manifests(manifest: Manifest, project: ProjectEntry) -> None:
"""Check for child manifests within a project.

Args:
manifest (dfetch.manifest.manifest.Manifest): The parent manifest with projects.
project (ProjectEntry): The parent project.
path (str): The path of the parent manifest.
"""
for (
childmanifest,
childmanifest_path,
) in get_childmanifests(skip=[path]):
for childmanifest in get_childmanifests(skip=[manifest.path]):
recommendations: List[ProjectEntry] = []
for childproject in childmanifest.projects:
if childproject.remote_url not in [
Expand All @@ -32,10 +28,10 @@ def check_child_manifests(manifest: Manifest, project: ProjectEntry, path: str)
recommendations.append(childproject.as_recommendation())

if recommendations:
childmanifest_path = os.path.relpath(
childmanifest_path, start=os.path.dirname(path)
childmanifest_relpath = os.path.relpath(
childmanifest.path, start=os.path.dirname(manifest.path)
).replace("\\", "/")
_make_recommendation(project, recommendations, childmanifest_path)
_make_recommendation(project, recommendations, childmanifest_relpath)


def _make_recommendation(
Expand Down
8 changes: 4 additions & 4 deletions dfetch/commands/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Perform the diff."""
manifest, path = dfetch.manifest.manifest.get_manifest()
manifest = dfetch.manifest.manifest.get_manifest()
revs = [r for r in args.revs.strip(":").split(":", maxsplit=1) if r]

with in_directory(os.path.dirname(path)):
with in_directory(os.path.dirname(manifest.path)):
exceptions: List[str] = []
projects = manifest.selected_projects(args.projects)
Comment thread
spoorcc marked this conversation as resolved.
if not projects:
Expand All @@ -113,10 +113,10 @@ def __call__(self, args: argparse.Namespace) -> None:
for project in projects:
patch_name = f"{project.name}.patch"
with catch_runtime_exceptions(exceptions) as exceptions:
repo = _get_repo(path, project)
repo = _get_repo(manifest.path, project)
patch = _diff_from_repo(repo, project, revs)

_dump_patch(path, revs, project, patch_name, patch)
_dump_patch(manifest.path, revs, project, patch_name, patch)

if exceptions:
raise RuntimeError("\n".join(exceptions))
Expand Down
4 changes: 2 additions & 2 deletions dfetch/commands/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,12 @@ def __call__(self, args: argparse.Namespace) -> None:
"""Perform the freeze."""
del args # unused

manifest, path = get_manifest()
manifest = get_manifest()

exceptions: List[str] = []
projects: List[ProjectEntry] = []

with in_directory(os.path.dirname(path)):
with in_directory(os.path.dirname(manifest.path)):
for project in manifest.projects:
with catch_runtime_exceptions(exceptions) as exceptions:
on_disk_version = dfetch.project.make(project).on_disk_version()
Expand Down
45 changes: 26 additions & 19 deletions dfetch/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
import argparse
import glob
import os

import infer_license
from typing import List

import dfetch.commands.command
import dfetch.manifest.manifest
Expand All @@ -17,9 +16,13 @@
from dfetch.project.metadata import Metadata
from dfetch.project.vcs import VCS
from dfetch.reporting import REPORTERS, ReportTypes
from dfetch.util.license import License, guess_license_in_file

logger = get_logger(__name__)

# Only accept license guesses with below or higher confidence to avoid false positives
LICENSE_PROBABILITY_THRESHOLD = 0.80
Comment thread
spoorcc marked this conversation as resolved.


class Report(dfetch.commands.command.Command):
"""Generate reports containing information about the projects components.
Expand Down Expand Up @@ -60,43 +63,47 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Generate the report."""
manifest, path = dfetch.manifest.manifest.get_manifest()
manifest = dfetch.manifest.manifest.get_manifest()

reporter = REPORTERS[args.type]()
with dfetch.util.util.in_directory(os.path.dirname(manifest.path)):
reporter = REPORTERS[args.type](manifest)

with dfetch.util.util.in_directory(os.path.dirname(path)):
for project in manifest.selected_projects(args.projects):
determined_license = self._determine_license(project)
determined_licenses = self._determine_licenses(project)
version = self._determine_version(project)
reporter.add_project(
project=project, license_name=determined_license, version=version
project=project, licenses=determined_licenses, version=version
)

if reporter.dump_to_file(args.outfile):
logger.info(f"Generated {reporter.name} report: {args.outfile}")

@staticmethod
def _determine_license(project: ProjectEntry) -> str:
def _determine_licenses(project: ProjectEntry) -> List[License]:
"""Try to determine license of fetched project."""
if not os.path.exists(project.destination):
logger.print_warning_line(
project.name, "Never fetched, fetch it to get license info."
)
return ""
return []

license_files = []
with dfetch.util.util.in_directory(project.destination):

for license_file in filter(VCS.is_license_file, glob.glob("*")):
logger.debug(f"Found license file {license_file} for {project.name}")
guessed_license = infer_license.api.guess_file(license_file)

if guessed_license:
return str(guessed_license.name)

logger.print_warning_line(
project.name, f"Could not determine license in {license_file}"
)

return ""
guessed_license = guess_license_in_file(license_file)
Comment thread
spoorcc marked this conversation as resolved.

if (
guessed_license
and guessed_license.probability > LICENSE_PROBABILITY_THRESHOLD
):
license_files.append(guessed_license)
else:
logger.print_warning_line(
project.name, f"Could not determine license in {license_file}"
)
Comment thread
spoorcc marked this conversation as resolved.
return license_files

@staticmethod
def _determine_version(project: ProjectEntry) -> str:
Expand Down
6 changes: 3 additions & 3 deletions dfetch/commands/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Perform the update."""
manifest, path = dfetch.manifest.manifest.get_manifest()
manifest = dfetch.manifest.manifest.get_manifest()

exceptions: List[str] = []
destinations: List[str] = [
os.path.realpath(project.destination) for project in manifest.projects
]
with in_directory(os.path.dirname(path)):
with in_directory(os.path.dirname(manifest.path)):
for project in manifest.selected_projects(args.projects):
with catch_runtime_exceptions(exceptions) as exceptions:
self._check_destination(project, destinations)
Expand All @@ -85,7 +85,7 @@ def __call__(self, args: argparse.Namespace) -> None:
project.destination
):
with in_directory(project.destination):
check_child_manifests(manifest, project, path)
check_child_manifests(manifest, project)

if exceptions:
raise RuntimeError("\n".join(exceptions))
Expand Down
Loading
Loading