|
| 1 | +# Copyright (c) 2025 - 2025, Oracle and/or its affiliates. All rights reserved. |
| 2 | +# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/. |
| 3 | + |
| 4 | +"""This module contains the implementation of the build command patching.""" |
| 5 | + |
| 6 | +import logging |
| 7 | +from collections.abc import Mapping, Sequence |
| 8 | + |
| 9 | +from macaron.build_spec_generator.cli_command_parser import CLICommand, CLICommandParser, PatchCommandBuildTool |
| 10 | +from macaron.build_spec_generator.cli_command_parser.gradle_cli_parser import ( |
| 11 | + GradleCLICommandParser, |
| 12 | + GradleOptionPatchValueType, |
| 13 | +) |
| 14 | +from macaron.build_spec_generator.cli_command_parser.maven_cli_parser import ( |
| 15 | + CommandLineParseError, |
| 16 | + MavenCLICommandParser, |
| 17 | + MavenOptionPatchValueType, |
| 18 | + PatchBuildCommandError, |
| 19 | +) |
| 20 | +from macaron.build_spec_generator.cli_command_parser.unparsed_cli_command import UnparsedCLICommand |
| 21 | + |
| 22 | +logger: logging.Logger = logging.getLogger(__name__) |
| 23 | + |
| 24 | +MVN_CLI_PARSER = MavenCLICommandParser() |
| 25 | +GRADLE_CLI_PARSER = GradleCLICommandParser() |
| 26 | + |
| 27 | +PatchValueType = GradleOptionPatchValueType | MavenOptionPatchValueType |
| 28 | + |
| 29 | + |
| 30 | +def _patch_commands( |
| 31 | + cmds_sequence: Sequence[list[str]], |
| 32 | + cli_parsers: Sequence[CLICommandParser], |
| 33 | + patches: Mapping[ |
| 34 | + PatchCommandBuildTool, |
| 35 | + Mapping[str, PatchValueType | None], |
| 36 | + ], |
| 37 | +) -> list[CLICommand] | None: |
| 38 | + """Patch the sequence of build commands, using the provided CLICommandParser instances. |
| 39 | +
|
| 40 | + For each command in `cmds_sequence`, it will be checked against all CLICommandParser instances until there is |
| 41 | + one that can parse it, then a patch from ``patches`` is applied for this command if provided. |
| 42 | +
|
| 43 | + If a command doesn't have any corresponding ``CLICommandParser`` instance it will be parsed as UnparsedCLICommand, |
| 44 | + which just holds the original command as a list of string, without any changes. |
| 45 | + """ |
| 46 | + result: list[CLICommand] = [] |
| 47 | + for cmds in cmds_sequence: |
| 48 | + effective_cli_parser = None |
| 49 | + for cli_parser in cli_parsers: |
| 50 | + if cli_parser.is_build_tool(cmds[0]): |
| 51 | + effective_cli_parser = cli_parser |
| 52 | + break |
| 53 | + |
| 54 | + if not effective_cli_parser: |
| 55 | + result.append(UnparsedCLICommand(original_cmds=cmds)) |
| 56 | + continue |
| 57 | + |
| 58 | + try: |
| 59 | + cli_command = effective_cli_parser.parse(cmds) |
| 60 | + except CommandLineParseError as error: |
| 61 | + logger.error( |
| 62 | + "Failed to parse the mvn command %s. Error %s.", |
| 63 | + " ".join(cmds), |
| 64 | + error, |
| 65 | + ) |
| 66 | + return None |
| 67 | + |
| 68 | + patch = patches.get(effective_cli_parser.build_tool, None) |
| 69 | + if not patch: |
| 70 | + result.append(cli_command) |
| 71 | + continue |
| 72 | + |
| 73 | + try: |
| 74 | + new_cli_command = effective_cli_parser.apply_patch( |
| 75 | + cli_command=cli_command, |
| 76 | + options_patch=patch, |
| 77 | + ) |
| 78 | + except PatchBuildCommandError as error: |
| 79 | + logger.error( |
| 80 | + "Failed to patch the mvn command %s. Error %s.", |
| 81 | + " ".join(cmds), |
| 82 | + error, |
| 83 | + ) |
| 84 | + return None |
| 85 | + |
| 86 | + result.append(new_cli_command) |
| 87 | + |
| 88 | + return result |
| 89 | + |
| 90 | + |
| 91 | +def patch_commands( |
| 92 | + cmds_sequence: Sequence[list[str]], |
| 93 | + patches: Mapping[ |
| 94 | + PatchCommandBuildTool, |
| 95 | + Mapping[str, PatchValueType | None], |
| 96 | + ], |
| 97 | +) -> list[list[str]] | None: |
| 98 | + """Patch a sequence of CLI commands. |
| 99 | +
|
| 100 | + For each command in this command sequence: |
| 101 | +
|
| 102 | + - If the command is not a build command or the build tool is not supported by us, it will be leave intact. |
| 103 | +
|
| 104 | + - If the command is a build command supported by us, it will be patch if a patch value is provided to ``patches``. |
| 105 | + If no patch value is provided for a build command, it will be leave intact. |
| 106 | +
|
| 107 | + `patches` is a mapping with: |
| 108 | +
|
| 109 | + - **Key**: an instance of the ``BuildTool`` enum |
| 110 | +
|
| 111 | + - **Value**: the patch value provided to ``CLICommandParser.apply_patch``. For more information on the patch value |
| 112 | + see the concrete implementations of the ``CLICommandParser.apply_patch`` method. |
| 113 | + For example: :class:`macaron.cli_command_parser.maven_cli_parser.MavenCLICommandParser.apply_patch`, |
| 114 | + :class:`macaron.cli_command_parser.gradle_cli_parser.GradleCLICommandParser.apply_patch`. |
| 115 | +
|
| 116 | + This means that all commands that matches a BuildTool will be apply by the same patch value. |
| 117 | +
|
| 118 | + Returns |
| 119 | + ------- |
| 120 | + list[list[str]] | None |
| 121 | + The patched command sequence or None if there is an error. The errors that can happen if any command |
| 122 | + which we support is invalid in ``cmds_sequence``, or the patch value is valid. |
| 123 | + """ |
| 124 | + result = [] |
| 125 | + patch_cli_commands = _patch_commands( |
| 126 | + cmds_sequence=cmds_sequence, |
| 127 | + cli_parsers=[MVN_CLI_PARSER, GRADLE_CLI_PARSER], |
| 128 | + patches=patches, |
| 129 | + ) |
| 130 | + |
| 131 | + if patch_cli_commands is None: |
| 132 | + return None |
| 133 | + |
| 134 | + for patch_cmd in patch_cli_commands: |
| 135 | + result.append(patch_cmd.to_cmds()) |
| 136 | + |
| 137 | + return result |
0 commit comments