Skip to content

Commit e31cecb

Browse files
authored
feat!: extend build command information in buildspec (#1308)
This PR improves build tool detection and buildspec generation across ecosystems, with stronger support for multi-module Java projects and richer build command metadata. There are breaking changes in the schema of macaron.buildspec and build_tool_check table. Signed-off-by: behnazh-w <behnaz.hassanshahi@oracle.com>
1 parent 452c9c5 commit e31cecb

70 files changed

Lines changed: 2715 additions & 750 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/macaron/build_spec_generator/build_command_patcher.py

Lines changed: 58 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -65,80 +65,74 @@
6565
}
6666

6767

68-
def _patch_commands(
69-
cmds_sequence: Sequence[list[str]],
68+
def _patch_command(
69+
cmd: list[str],
7070
cli_parsers: Sequence[CLICommandParser],
7171
patches: Mapping[
7272
PatchCommandBuildTool,
7373
Mapping[str, PatchValueType | None],
7474
],
75-
) -> list[CLICommand] | None:
76-
"""Patch the sequence of build commands, using the provided CLICommandParser instances.
75+
) -> CLICommand | None:
76+
"""Patch the build command, using the provided CLICommandParser instances.
7777
78-
For each command in `cmds_sequence`, it will be checked against all CLICommandParser instances until there is
78+
The command will be checked against all CLICommandParser instances to find
7979
one that can parse it, then a patch from ``patches`` is applied for this command if provided.
8080
8181
If a command doesn't have any corresponding ``CLICommandParser`` instance it will be parsed as UnparsedCLICommand,
8282
which just holds the original command as a list of string, without any changes.
8383
"""
84-
result: list[CLICommand] = []
85-
for cmd in cmds_sequence:
86-
# Checking if the command is a valid non-empty list.
87-
if not cmd:
88-
continue
89-
effective_cli_parser = None
90-
for cli_parser in cli_parsers:
91-
if cli_parser.is_build_tool(cmd[0]):
92-
effective_cli_parser = cli_parser
93-
break
94-
95-
if not effective_cli_parser:
96-
result.append(UnparsedCLICommand(original_cmds=cmd))
97-
continue
98-
99-
try:
100-
cli_command = effective_cli_parser.parse(cmd)
101-
except CommandLineParseError as error:
102-
logger.error(
103-
"Failed to patch the cli command %s. Error %s.",
104-
" ".join(cmd),
105-
error,
106-
)
107-
return None
108-
109-
patch = patches.get(effective_cli_parser.build_tool, None)
110-
if not patch:
111-
result.append(cli_command)
112-
continue
113-
114-
try:
115-
new_cli_command = effective_cli_parser.apply_patch(
116-
cli_command=cli_command,
117-
patch_options=patch,
118-
)
119-
except PatchBuildCommandError as error:
120-
logger.error(
121-
"Failed to patch the build command %s. Error %s.",
122-
" ".join(cmd),
123-
error,
124-
)
125-
return None
126-
127-
result.append(new_cli_command)
128-
129-
return result
130-
131-
132-
def patch_commands(
133-
cmds_sequence: Sequence[list[str]],
84+
# Checking if the command is a valid non-empty list.
85+
if not cmd:
86+
return None
87+
88+
effective_cli_parser = None
89+
for cli_parser in cli_parsers:
90+
if cli_parser.is_build_tool(cmd[0]):
91+
effective_cli_parser = cli_parser
92+
break
93+
94+
if not effective_cli_parser:
95+
return UnparsedCLICommand(original_cmds=cmd)
96+
97+
try:
98+
cli_command = effective_cli_parser.parse(cmd)
99+
except CommandLineParseError as error:
100+
logger.error(
101+
"Failed to patch the cli command %s. Error %s.",
102+
" ".join(cmd),
103+
error,
104+
)
105+
return None
106+
107+
patch = patches.get(effective_cli_parser.build_tool, None)
108+
if not patch:
109+
return cli_command
110+
111+
try:
112+
patched_command: CLICommand = effective_cli_parser.apply_patch(
113+
cli_command=cli_command,
114+
patch_options=patch,
115+
)
116+
return patched_command
117+
except PatchBuildCommandError as error:
118+
logger.error(
119+
"Failed to patch the build command %s. Error %s.",
120+
" ".join(cmd),
121+
error,
122+
)
123+
return None
124+
125+
126+
def patch_command(
127+
cmd: list[str],
134128
patches: Mapping[
135129
PatchCommandBuildTool,
136130
Mapping[str, PatchValueType | None],
137131
],
138-
) -> list[list[str]] | None:
139-
"""Patch a sequence of CLI commands.
132+
) -> list[str] | None:
133+
"""Patch a CLI command.
140134
141-
For each command in this command sequence:
135+
Possible scenarios:
142136
143137
- If the command is not a build command, or it's a tool we do not support, it will be left intact.
144138
@@ -158,21 +152,16 @@ def patch_commands(
158152
159153
Returns
160154
-------
161-
list[list[str]] | None
162-
The patched command sequence or None if there is an error. The errors that can happen if any command
163-
which we support is invalid in ``cmds_sequence``, or the patch value is valid.
155+
list[str] | None
156+
The patched command or None if there is an error.
164157
"""
165-
result = []
166-
patch_cli_commands = _patch_commands(
167-
cmds_sequence=cmds_sequence,
158+
patch_cli_command = _patch_command(
159+
cmd=cmd,
168160
cli_parsers=[MVN_CLI_PARSER, GRADLE_CLI_PARSER],
169161
patches=patches,
170162
)
171163

172-
if patch_cli_commands is None:
164+
if patch_cli_command is None:
173165
return None
174166

175-
for patch_cmd in patch_cli_commands:
176-
result.append(patch_cmd.to_cmds())
177-
178-
return result
167+
return patch_cli_command.to_cmds()

src/macaron/build_spec_generator/common_spec/base_spec.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,32 @@
99
from packageurl import PackageURL
1010

1111

12+
class SpecBuildCommandDict(TypedDict, total=False):
13+
"""
14+
Initialize build command section of the build specification.
15+
16+
It contains helpful information related to a build command.
17+
"""
18+
19+
#: The build tool.
20+
build_tool: Required[str]
21+
22+
#: The build tool version.
23+
build_tool_version: NotRequired[str]
24+
25+
#: The build configuration path
26+
build_config_path: Required[str]
27+
28+
#: The root build configuration path if present
29+
root_build_config_path: NotRequired[str]
30+
31+
#: The build command.
32+
command: Required[list[str]]
33+
34+
#: The confidence score for the analysis result that has inferred the build tool information.
35+
confidence_score: Required[float]
36+
37+
1238
class BaseBuildSpecDict(TypedDict, total=False):
1339
"""
1440
Initialize base build specification.
@@ -58,8 +84,8 @@ class BaseBuildSpecDict(TypedDict, total=False):
5884
#: List of build dependencies, which includes tests.
5985
build_dependencies: NotRequired[list[str]]
6086

61-
#: List of shell commands to build the project.
62-
build_commands: NotRequired[list[list[str]]]
87+
#: List of shell commands and related information to build the project.
88+
build_commands: NotRequired[list[SpecBuildCommandDict]]
6389

6490
#: List of shell commands to test the project.
6591
test_commands: NotRequired[list[list[str]]]
@@ -103,24 +129,14 @@ def resolve_fields(self, purl: PackageURL) -> None:
103129
"""
104130

105131
@abstractmethod
106-
def get_default_build_commands(
132+
def set_default_build_commands(
107133
self,
108-
build_tool_names: list[str],
109-
) -> list[list[str]]:
134+
build_cmd_spec: SpecBuildCommandDict,
135+
) -> None:
110136
"""Return the default build commands for the build tools.
111137
112138
Parameters
113139
----------
114-
build_tool_names: list[str]
115-
The build tools to get the default build command.
116-
117-
Returns
118-
-------
119-
list[list[str]]
120-
The build command as a list[list[str]].
121-
122-
Raises
123-
------
124-
GenerateBuildSpecError
125-
If there is no default build command available for the specified build tool.
140+
build_cmd_spec: SpecBuildCommandDict
141+
The build command and related information.
126142
"""

0 commit comments

Comments
 (0)