Skip to content
Draft
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
128 changes: 59 additions & 69 deletions src/macaron/build_spec_generator/build_command_patcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,80 +66,74 @@
}


def _patch_commands(
cmds_sequence: Sequence[list[str]],
def _patch_command(
cmd: list[str],
cli_parsers: Sequence[CLICommandParser],
patches: Mapping[
PatchCommandBuildTool,
Mapping[str, PatchValueType | None],
],
) -> list[CLICommand] | None:
"""Patch the sequence of build commands, using the provided CLICommandParser instances.
) -> CLICommand | None:
"""Patch the build command, using the provided CLICommandParser instances.

For each command in `cmds_sequence`, it will be checked against all CLICommandParser instances until there is
The command will be checked against all CLICommandParser instances to find
one that can parse it, then a patch from ``patches`` is applied for this command if provided.

If a command doesn't have any corresponding ``CLICommandParser`` instance it will be parsed as UnparsedCLICommand,
which just holds the original command as a list of string, without any changes.
"""
result: list[CLICommand] = []
for cmd in cmds_sequence:
# Checking if the command is a valid non-empty list.
if not cmd:
continue
effective_cli_parser = None
for cli_parser in cli_parsers:
if cli_parser.is_build_tool(cmd[0]):
effective_cli_parser = cli_parser
break

if not effective_cli_parser:
result.append(UnparsedCLICommand(original_cmds=cmd))
continue

try:
cli_command = effective_cli_parser.parse(cmd)
except CommandLineParseError as error:
logger.error(
"Failed to patch the cli command %s. Error %s.",
" ".join(cmd),
error,
)
return None

patch = patches.get(effective_cli_parser.build_tool, None)
if not patch:
result.append(cli_command)
continue

try:
new_cli_command = effective_cli_parser.apply_patch(
cli_command=cli_command,
patch_options=patch,
)
except PatchBuildCommandError as error:
logger.error(
"Failed to patch the build command %s. Error %s.",
" ".join(cmd),
error,
)
return None

result.append(new_cli_command)

return result


def patch_commands(
cmds_sequence: Sequence[list[str]],
# Checking if the command is a valid non-empty list.
if not cmd:
return None

effective_cli_parser = None
for cli_parser in cli_parsers:
if cli_parser.is_build_tool(cmd[0]):
effective_cli_parser = cli_parser
break

if not effective_cli_parser:
return UnparsedCLICommand(original_cmds=cmd)

try:
cli_command = effective_cli_parser.parse(cmd)
except CommandLineParseError as error:
logger.error(
"Failed to patch the cli command %s. Error %s.",
" ".join(cmd),
error,
)
return None

patch = patches.get(effective_cli_parser.build_tool, None)
if not patch:
return cli_command

try:
patched_command: CLICommand = effective_cli_parser.apply_patch(
cli_command=cli_command,
patch_options=patch,
)
return patched_command
except PatchBuildCommandError as error:
logger.error(
"Failed to patch the build command %s. Error %s.",
" ".join(cmd),
error,
)
return None


def patch_command(
cmd: list[str],
patches: Mapping[
PatchCommandBuildTool,
Mapping[str, PatchValueType | None],
],
) -> list[list[str]] | None:
"""Patch a sequence of CLI commands.
) -> list[str] | None:
"""Patch a CLI command.

For each command in this command sequence:
Possible scenarios:

- If the command is not a build command, or it's a tool we do not support, it will be left intact.

Expand All @@ -159,21 +153,17 @@ def patch_commands(

Returns
-------
list[list[str]] | None
The patched command sequence or None if there is an error. The errors that can happen if any command
which we support is invalid in ``cmds_sequence``, or the patch value is valid.
list[str] | None
The patched command or None if there is an error. An error happens if a command,
or the patch value is valid.
"""
result = []
patch_cli_commands = _patch_commands(
cmds_sequence=cmds_sequence,
patch_cli_command = _patch_command(
cmd=cmd,
cli_parsers=[MVN_CLI_PARSER, GRADLE_CLI_PARSER],
patches=patches,
)

if patch_cli_commands is None:
if patch_cli_command is None:
return None

for patch_cmd in patch_cli_commands:
result.append(patch_cmd.to_cmds())

return result
return patch_cli_command.to_cmds()
29 changes: 24 additions & 5 deletions src/macaron/build_spec_generator/common_spec/base_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,25 @@
from packageurl import PackageURL


class SpecBuildCommandDict(TypedDict, total=False):
"""
Initialize build command section of the build specification.

It contains helpful information related to a build command.
"""

#: The build tool.
build_tool: Required[str]

#: The build tool version.
build_tool_version: NotRequired[str]

#: The build configuration path
build_tool_path: NotRequired[str]

#: The build command.
command: Required[list[str]]

class BaseBuildSpecDict(TypedDict, total=False):
"""
Initialize base build specification.
Expand Down Expand Up @@ -58,8 +77,8 @@ class BaseBuildSpecDict(TypedDict, total=False):
#: List of build dependencies, which includes tests.
build_dependencies: NotRequired[list[str]]

#: List of shell commands to build the project.
build_commands: NotRequired[list[list[str]]]
#: List of shell commands and related information to build the project.
build_commands: NotRequired[list[SpecBuildCommandDict]]

#: List of shell commands to test the project.
test_commands: NotRequired[list[list[str]]]
Expand Down Expand Up @@ -106,7 +125,7 @@ def resolve_fields(self, purl: PackageURL) -> None:
def get_default_build_commands(
self,
build_tool_names: list[str],
) -> list[list[str]]:
) -> list[SpecBuildCommandDict]:
"""Return the default build commands for the build tools.

Parameters
Expand All @@ -116,8 +135,8 @@ def get_default_build_commands(

Returns
-------
list[list[str]]
The build command as a list[list[str]].
list[SpecBuildCommandDict]
The build command and relevant information as a list[SpecBuildCommandDict].

Raises
------
Expand Down
33 changes: 20 additions & 13 deletions src/macaron/build_spec_generator/common_spec/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2025 - 2026, Oracle and/or its affiliates. All rights reserved.
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/.

"""This module contains the logic to generate a build spec in a generic format that can be transformed if needed."""
Expand All @@ -13,7 +13,7 @@
import sqlalchemy.orm
from packageurl import PackageURL

from macaron.build_spec_generator.common_spec.base_spec import BaseBuildSpecDict
from macaron.build_spec_generator.common_spec.base_spec import BaseBuildSpecDict, SpecBuildCommandDict
from macaron.build_spec_generator.common_spec.maven_spec import MavenBuildSpec
from macaron.build_spec_generator.common_spec.pypi_spec import PyPIBuildSpec
from macaron.build_spec_generator.macaron_db_extractor import (
Expand Down Expand Up @@ -75,8 +75,8 @@ def format_build_command_info(build_command_info: list[GenericBuildCommandInfo])
str
The prettified output.
"""
pretty_formatted_ouput = [pprint.pformat(build_command_info) for build_command_info in build_command_info]
return "\n".join(pretty_formatted_ouput)
pretty_formatted_output = [pprint.pformat(build_command_info) for build_command_info in build_command_info]
return "\n".join(pretty_formatted_output)


def remove_shell_quote(cmd: list[str]) -> list[str]:
Expand Down Expand Up @@ -351,18 +351,22 @@ def gen_generic_build_spec(
if build_tools is not None:
build_tool_names = [build_tool.value for build_tool in build_tools]

build_command_info = get_build_command_info(
db_build_command_info = get_build_command_info(
component_id=latest_component.id,
session=session,
)
logger.info(
"Attempted to find build command from the database. Result: %s",
build_command_info or "Cannot find any.",
)

selected_build_command = build_command_info.command if build_command_info else []

lang_version = get_language_version(build_command_info) if build_command_info else ""
lang_version = None
spec_build_commad_info = None
if db_build_command_info:
logger.info(
"Attempted to find build command from the database. Result: %s",
db_build_command_info or "Cannot find any.",
)
lang_version = get_language_version(db_build_command_info) if db_build_command_info else ""
spec_build_commad_info = SpecBuildCommandDict(
build_tool=db_build_command_info.build_tool_name, command=db_build_command_info.command
)

base_build_spec_dict = BaseBuildSpecDict(
{
Expand All @@ -378,8 +382,11 @@ def gen_generic_build_spec(
"purl": str(purl),
"language": target_language,
"build_tools": build_tool_names,
"build_commands": [selected_build_command] if selected_build_command else [],
"build_commands": (
[spec_build_commad_info] if spec_build_commad_info and spec_build_commad_info["command"] else []
),
}
)

ECOSYSTEMS[purl.type.upper()].value(base_build_spec_dict).resolve_fields(purl)
return base_build_spec_dict
50 changes: 27 additions & 23 deletions src/macaron/build_spec_generator/common_spec/maven_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@

from packageurl import PackageURL

from macaron.build_spec_generator.build_command_patcher import CLI_COMMAND_PATCHES, patch_commands
from macaron.build_spec_generator.common_spec.base_spec import BaseBuildSpec, BaseBuildSpecDict
from macaron.build_spec_generator.build_command_patcher import CLI_COMMAND_PATCHES, patch_command
from macaron.build_spec_generator.common_spec.base_spec import BaseBuildSpec, BaseBuildSpecDict, SpecBuildCommandDict
from macaron.build_spec_generator.common_spec.jdk_finder import find_jdk_version_from_central_maven_repo
from macaron.build_spec_generator.common_spec.jdk_version_normalizer import normalize_jdk_version

Expand All @@ -33,7 +33,7 @@ def __init__(self, data: BaseBuildSpecDict):
def get_default_build_commands(
self,
build_tool_names: list[str],
) -> list[list[str]]:
) -> list[SpecBuildCommandDict]:
"""Return the default build commands for the build tools.

Parameters
Expand All @@ -43,28 +43,34 @@ def get_default_build_commands(

Returns
-------
list[list[str]]
The build command as a list[list[str]].
list[SpecBuildCommandDict]
The build command as a list[SpecBuildCommandDict].
"""
default_build_commands = []
default_build_cmd_list = []

for build_tool_name in build_tool_names:

match build_tool_name:
case "maven":
default_build_commands.append("mvn clean package".split())
default_build_cmd_list.append(
SpecBuildCommandDict(build_tool=build_tool_name, command="mvn clean package".split())
)
case "gradle":
default_build_commands.append("./gradlew clean assemble publishToMavenLocal".split())
default_build_cmd_list.append(
SpecBuildCommandDict(
build_tool=build_tool_name, command="./gradlew clean assemble publishToMavenLocal".split()
)
)
case _:
pass

if not default_build_commands:
if not default_build_cmd_list:
logger.debug(
"There is no default build command available for the build tools %s.",
build_tool_names,
)

return default_build_commands
return default_build_cmd_list

def resolve_fields(self, purl: PackageURL) -> None:
"""
Expand Down Expand Up @@ -108,16 +114,14 @@ def resolve_fields(self, purl: PackageURL) -> None:
self.data["language_version"] = [major_jdk_version]

# Resolve and patch build commands.
selected_build_commands = self.data["build_commands"] or self.get_default_build_commands(
self.data["build_tools"]
)
patched_build_commands = patch_commands(
cmds_sequence=selected_build_commands,
patches=CLI_COMMAND_PATCHES,
)
if not patched_build_commands:
logger.debug("Failed to patch build command sequences %s", selected_build_commands)
self.data["build_commands"] = []
return

self.data["build_commands"] = patched_build_commands
if not self.data["build_commands"]:
self.data["build_commands"] = self.get_default_build_commands(self.data["build_tools"])

for build_command_info in self.data["build_commands"]:
if build_command_info["command"] and (
patched_cmd := patch_command(
cmd=build_command_info["command"],
patches=CLI_COMMAND_PATCHES,
)
):
build_command_info["command"] = patched_cmd
Loading