Skip to content

Commit 81d6f60

Browse files
committed
feat: add basic support for Python in gen-build-spec
Signed-off-by: behnazh-w <[email protected]>
1 parent 736dbf8 commit 81d6f60

File tree

22 files changed

+1015
-593
lines changed

22 files changed

+1015
-593
lines changed

src/macaron/__main__.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,8 +583,10 @@ def main(argv: list[str] | None = None) -> None:
583583
gen_build_spec_parser.add_argument(
584584
"--output-format",
585585
type=str,
586-
help=('The output format. Can be rc-buildspec (Reproducible-central build spec) (default "rc-buildspec")'),
587-
default="rc-buildspec",
586+
help=(
587+
"The output format. Can be default-buildspec (default) or rc-buildspec (Reproducible-central build spec)"
588+
),
589+
default="default-buildspec",
588590
)
589591

590592
args = main_parser.parse_args(argv)

src/macaron/build_spec_generator/build_spec_generator.py

Lines changed: 46 additions & 24 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,7 +14,9 @@
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
19+
from macaron.errors import GenerateBuildSpecError
1720
from macaron.path_utils.purl_based_path import get_purl_based_dir
1821

1922
logger: logging.Logger = logging.getLogger(__name__)
@@ -24,6 +27,8 @@ class BuildSpecFormat(str, Enum):
2427

2528
REPRODUCIBLE_CENTRAL = "rc-buildspec"
2629

30+
DEFAULT = "default-buildspec"
31+
2732

2833
CLI_COMMAND_PATCHES: dict[
2934
PatchCommandBuildTool,
@@ -95,49 +100,66 @@ def gen_build_spec_for_purl(
95100
db_engine = create_engine(f"sqlite+pysqlite:///file:{database_path}?mode=ro&uri=true", echo=False)
96101
build_spec_content = None
97102

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

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

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

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

129151
logger.info(
130-
"Generating the %s format build spec to %s.",
152+
"Generating the %s format build spec to %s",
131153
build_spec_format.value,
132-
os.path.relpath(build_spec_filepath, os.getcwd()),
154+
os.path.relpath(build_spec_file_path, os.getcwd()),
133155
)
134156
try:
135-
with open(build_spec_filepath, mode="w", encoding="utf-8") as file:
157+
with open(build_spec_file_path, mode="w", encoding="utf-8") as file:
136158
file.write(build_spec_content)
137159
except OSError as error:
138160
logger.error(
139161
"Could not create the build spec at %s. Error: %s",
140-
os.path.relpath(build_spec_filepath, os.getcwd()),
162+
os.path.relpath(build_spec_file_path, os.getcwd()),
141163
error,
142164
)
143165
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)