Skip to content

Commit 4172e54

Browse files
authored
feat: add basic support for Python in gen-build-spec (#1203)
This PR adds basic support for Python projects to the gen-build-spec feature in Macaron. It also introduces a new approach for generic specification generation. Signed-off-by: behnazh-w <[email protected]>
1 parent 4a68267 commit 4172e54

File tree

32 files changed

+1386
-630
lines changed

32 files changed

+1386
-630
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
macaron.build\_spec\_generator.common\_spec package
2+
===================================================
3+
4+
.. automodule:: macaron.build_spec_generator.common_spec
5+
:members:
6+
:show-inheritance:
7+
:undoc-members:
8+
9+
Submodules
10+
----------
11+
12+
macaron.build\_spec\_generator.common\_spec.base\_spec module
13+
-------------------------------------------------------------
14+
15+
.. automodule:: macaron.build_spec_generator.common_spec.base_spec
16+
:members:
17+
:show-inheritance:
18+
:undoc-members:
19+
20+
macaron.build\_spec\_generator.common\_spec.core module
21+
-------------------------------------------------------
22+
23+
.. automodule:: macaron.build_spec_generator.common_spec.core
24+
:members:
25+
:show-inheritance:
26+
:undoc-members:
27+
28+
macaron.build\_spec\_generator.common\_spec.jdk\_finder module
29+
--------------------------------------------------------------
30+
31+
.. automodule:: macaron.build_spec_generator.common_spec.jdk_finder
32+
:members:
33+
:show-inheritance:
34+
:undoc-members:
35+
36+
macaron.build\_spec\_generator.common\_spec.jdk\_version\_normalizer module
37+
---------------------------------------------------------------------------
38+
39+
.. automodule:: macaron.build_spec_generator.common_spec.jdk_version_normalizer
40+
:members:
41+
:show-inheritance:
42+
:undoc-members:
43+
44+
macaron.build\_spec\_generator.common\_spec.maven\_spec module
45+
--------------------------------------------------------------
46+
47+
.. automodule:: macaron.build_spec_generator.common_spec.maven_spec
48+
:members:
49+
:show-inheritance:
50+
:undoc-members:
51+
52+
macaron.build\_spec\_generator.common\_spec.pypi\_spec module
53+
-------------------------------------------------------------
54+
55+
.. automodule:: macaron.build_spec_generator.common_spec.pypi_spec
56+
:members:
57+
:show-inheritance:
58+
:undoc-members:

docs/source/pages/developers_guide/apidoc/macaron.build_spec_generator.rst

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Subpackages
1313
:maxdepth: 1
1414

1515
macaron.build_spec_generator.cli_command_parser
16+
macaron.build_spec_generator.common_spec
1617
macaron.build_spec_generator.reproducible_central
1718

1819
Submodules
@@ -34,22 +35,6 @@ macaron.build\_spec\_generator.build\_spec\_generator module
3435
:show-inheritance:
3536
:undoc-members:
3637

37-
macaron.build\_spec\_generator.jdk\_finder module
38-
-------------------------------------------------
39-
40-
.. automodule:: macaron.build_spec_generator.jdk_finder
41-
:members:
42-
:show-inheritance:
43-
:undoc-members:
44-
45-
macaron.build\_spec\_generator.jdk\_version\_normalizer module
46-
--------------------------------------------------------------
47-
48-
.. automodule:: macaron.build_spec_generator.jdk_version_normalizer
49-
:members:
50-
:show-inheritance:
51-
:undoc-members:
52-
5338
macaron.build\_spec\_generator.macaron\_db\_extractor module
5439
------------------------------------------------------------
5540

src/macaron/__main__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -618,8 +618,10 @@ def main(argv: list[str] | None = None) -> None:
618618
gen_build_spec_parser.add_argument(
619619
"--output-format",
620620
type=str,
621-
help=('The output format. Can be rc-buildspec (Reproducible-central build spec) (default "rc-buildspec")'),
622-
default="rc-buildspec",
621+
help=(
622+
"The output format. Can be default-buildspec (default) or rc-buildspec (Reproducible-central build spec)"
623+
),
624+
default="default-buildspec",
623625
)
624626

625627
args = main_parser.parse_args(argv)

src/macaron/build_spec_generator/build_spec_generator.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
"""This module contains the functions used for generating build specs from the Macaron database."""
55

6+
import json
67
import logging
78
import os
89
from collections.abc import Mapping
@@ -13,8 +14,10 @@
1314
from sqlalchemy.orm import Session
1415

1516
from macaron.build_spec_generator.build_command_patcher import PatchCommandBuildTool, PatchValueType
17+
from macaron.build_spec_generator.common_spec.core import gen_generic_build_spec
1618
from macaron.build_spec_generator.reproducible_central.reproducible_central import gen_reproducible_central_build_spec
1719
from macaron.console import access_handler
20+
from macaron.errors import GenerateBuildSpecError
1821
from macaron.path_utils.purl_based_path import get_purl_based_dir
1922

2023
logger: logging.Logger = logging.getLogger(__name__)
@@ -25,6 +28,8 @@ class BuildSpecFormat(str, Enum):
2528

2629
REPRODUCIBLE_CENTRAL = "rc-buildspec"
2730

31+
DEFAULT = "default-buildspec"
32+
2833

2934
CLI_COMMAND_PATCHES: dict[
3035
PatchCommandBuildTool,
@@ -96,51 +101,68 @@ def gen_build_spec_for_purl(
96101
db_engine = create_engine(f"sqlite+pysqlite:///file:{database_path}?mode=ro&uri=true", echo=False)
97102
build_spec_content = None
98103

104+
build_spec_dir_path = os.path.join(
105+
output_path,
106+
"buildspec",
107+
get_purl_based_dir(
108+
purl_name=purl.name,
109+
purl_namespace=purl.namespace,
110+
purl_type=purl.type,
111+
),
112+
)
113+
99114
with Session(db_engine) as session, session.begin():
115+
try:
116+
build_spec = gen_generic_build_spec(purl=purl, session=session, patches=CLI_COMMAND_PATCHES)
117+
except GenerateBuildSpecError as error:
118+
logger.error("Error while generating the build spec: %s.", error)
119+
return os.EX_DATAERR
100120
match build_spec_format:
101121
case BuildSpecFormat.REPRODUCIBLE_CENTRAL:
102-
build_spec_content = gen_reproducible_central_build_spec(
103-
purl=purl,
104-
session=session,
105-
patches=CLI_COMMAND_PATCHES,
106-
)
122+
try:
123+
build_spec_content = gen_reproducible_central_build_spec(build_spec)
124+
except GenerateBuildSpecError as error:
125+
logger.error("Error while generating the build spec: %s.", error)
126+
return os.EX_DATAERR
127+
build_spec_file_path = os.path.join(build_spec_dir_path, "reproducible_central.buildspec")
128+
# Default build spec.
129+
case BuildSpecFormat.DEFAULT:
130+
try:
131+
build_spec_content = json.dumps(build_spec)
132+
except ValueError as error:
133+
logger.error("Error while serializing the build spec: %s.", error)
134+
return os.EX_DATAERR
135+
build_spec_file_path = os.path.join(build_spec_dir_path, "macaron.buildspec")
107136

108137
if not build_spec_content:
109138
logger.error("Error while generating the build spec.")
110139
return os.EX_DATAERR
111140

112141
logger.debug("Build spec content: \n%s", build_spec_content)
113142

114-
build_spec_filepath = os.path.join(
115-
output_path,
116-
"buildspec",
117-
get_purl_based_dir(
118-
purl_name=purl.name,
119-
purl_namespace=purl.namespace,
120-
purl_type=purl.type,
121-
),
122-
"macaron.buildspec",
123-
)
124-
125-
os.makedirs(
126-
name=os.path.dirname(build_spec_filepath),
127-
exist_ok=True,
128-
)
143+
try:
144+
os.makedirs(
145+
name=build_spec_dir_path,
146+
exist_ok=True,
147+
)
148+
except OSError as error:
149+
logger.error("Unable to create the output file: %s.", error)
150+
return os.EX_OSERR
129151

130152
logger.info(
131-
"Generating the %s format build spec to %s.",
153+
"Generating the %s format build spec to %s",
132154
build_spec_format.value,
133-
os.path.relpath(build_spec_filepath, os.getcwd()),
155+
os.path.relpath(build_spec_file_path, os.getcwd()),
134156
)
135157
rich_handler = access_handler.get_handler()
136-
rich_handler.update_gen_build_spec("Build Spec Path:", os.path.relpath(build_spec_filepath, os.getcwd()))
158+
rich_handler.update_gen_build_spec("Build Spec Path:", os.path.relpath(build_spec_file_path, os.getcwd()))
137159
try:
138-
with open(build_spec_filepath, mode="w", encoding="utf-8") as file:
160+
with open(build_spec_file_path, mode="w", encoding="utf-8") as file:
139161
file.write(build_spec_content)
140162
except OSError as error:
141163
logger.error(
142164
"Could not create the build spec at %s. Error: %s",
143-
os.path.relpath(build_spec_filepath, os.getcwd()),
165+
os.path.relpath(build_spec_file_path, os.getcwd()),
144166
error,
145167
)
146168
return os.EX_OSERR
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
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/.
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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 includes base build specification and helper classes."""
5+
6+
from abc import ABC, abstractmethod
7+
from typing import NotRequired, Required, TypedDict
8+
9+
from packageurl import PackageURL
10+
11+
12+
class BaseBuildSpecDict(TypedDict, total=False):
13+
"""
14+
Initialize base build specification.
15+
16+
It supports multiple languages, build tools, and additional metadata for enhanced traceability.
17+
"""
18+
19+
#: The package ecosystem.
20+
ecosystem: Required[str]
21+
22+
#: The package identifier.
23+
purl: Required[str]
24+
25+
#: The programming language, e.g., 'java', 'python', 'javascript'.
26+
language: Required[str]
27+
28+
#: The build tool or package manager, e.g., 'maven', 'gradle', 'pip', 'poetry', 'npm', 'yarn'.
29+
build_tool: Required[str]
30+
31+
#: The version of Macaron used for generating the spec.
32+
macaron_version: Required[str]
33+
34+
#: The group identifier for the project/component.
35+
group_id: NotRequired[str | None]
36+
37+
#: The artifact identifier for the project/component.
38+
artifact_id: Required[str]
39+
40+
#: The version of the package or component.
41+
version: Required[str]
42+
43+
#: The remote path or URL of the git repository.
44+
git_repo: NotRequired[str]
45+
46+
#: The commit SHA or tag in the VCS repository.
47+
git_tag: NotRequired[str]
48+
49+
#: The type of line endings used (e.g., 'lf', 'crlf').
50+
newline: NotRequired[str]
51+
52+
#: The version of the programming language or runtime, e.g., '11' for JDK, '3.11' for Python.
53+
language_version: Required[str]
54+
55+
#: List of release dependencies.
56+
dependencies: NotRequired[list[str]]
57+
58+
#: List of build dependencies, which includes tests.
59+
build_dependencies: NotRequired[list[str]]
60+
61+
#: List of shell commands to build the project.
62+
build_commands: NotRequired[list[list[str]]]
63+
64+
#: List of shell commands to test the project.
65+
test_commands: NotRequired[list[str]]
66+
67+
#: Environment variables required during build or test.
68+
environment: NotRequired[dict[str, str]]
69+
70+
#: Path or location of the build artifact/output.
71+
artifact_path: NotRequired[str | None]
72+
73+
#: Entry point script, class, or binary for running the project.
74+
entry_point: NotRequired[str | None]
75+
76+
77+
class BaseBuildSpec(ABC):
78+
"""Abstract base class for build specification behavior and field resolution."""
79+
80+
@abstractmethod
81+
def resolve_fields(self, purl: PackageURL) -> None:
82+
"""
83+
Resolve fields that require special logic for a specific build ecosystem.
84+
85+
Notes
86+
-----
87+
This method should be implemented by subclasses to handle
88+
logic specific to a given package ecosystem, such as Maven or PyPI.
89+
"""

0 commit comments

Comments
 (0)