Skip to content

Commit c32f539

Browse files
feat: sorted the build_requirements for stability, extended integration tests to compare dockerfiles, and added tests using this feature
Signed-off-by: Abhinav Pradeep <[email protected]>
1 parent e82df75 commit c32f539

File tree

9 files changed

+301
-0
lines changed

9 files changed

+301
-0
lines changed

src/macaron/build_spec_generator/dockerfile/pypi_dockerfile_output.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,6 @@ def build_backend_commands(buildspec: BaseBuildSpecDict) -> list[str]:
148148
commands: list[str] = []
149149
for backend, version_constraint in buildspec["build_requires"].items():
150150
commands.append(f'/deps/bin/pip install "{backend}{version_constraint}"')
151+
# For a stable order on the install commands
152+
commands.sort()
151153
return commands
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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+
"""Script to compare a generated dockerfile buildspec."""
5+
6+
import argparse
7+
import logging
8+
from collections.abc import Callable
9+
10+
logger = logging.getLogger(__name__)
11+
logger.setLevel(logging.DEBUG)
12+
logging.basicConfig(format="[%(filename)s:%(lineno)s %(tag)s] %(message)s")
13+
14+
15+
def log_with_tag(tag: str) -> Callable[[str], None]:
16+
"""Generate a log function that prints the name of the file and a tag at the beginning of each line."""
17+
18+
def log_fn(msg: str) -> None:
19+
logger.info(msg, extra={"tag": tag})
20+
21+
return log_fn
22+
23+
24+
log_info = log_with_tag("INFO")
25+
log_err = log_with_tag("ERROR")
26+
log_passed = log_with_tag("PASSED")
27+
log_failed = log_with_tag("FAILED")
28+
29+
30+
def log_diff(result: str, expected: str) -> None:
31+
"""Pretty-print the diff of two strings."""
32+
output = [
33+
*("---- Result ---", result),
34+
*("---- Expected ---", expected),
35+
"-----------------",
36+
]
37+
log_info("\n".join(output))
38+
39+
40+
def main() -> int:
41+
"""Compare a Macaron generated dockerfile buildspec.
42+
43+
Returns
44+
-------
45+
int
46+
0 if the generated dockerfile matches the expected output, or non-zero otherwise.
47+
"""
48+
parser = argparse.ArgumentParser()
49+
parser.add_argument("result_dockerfile", help="the result dockerfile buildspec")
50+
parser.add_argument("expected_dockerfile_buildspec", help="the expected buildspec dockerfile")
51+
args = parser.parse_args()
52+
53+
# Load both files
54+
with open(args.result_dockerfile, encoding="utf-8") as file:
55+
buildspec = normalize(file.read())
56+
57+
with open(args.expected_dockerfile_buildspec, encoding="utf-8") as file:
58+
expected_buildspec = normalize(file.read())
59+
60+
log_info(
61+
f"Comparing the dockerfile buildspec {args.result_dockerfile} with the expected "
62+
+ "output dockerfile {args.expected_dockerfile_buildspec}"
63+
)
64+
65+
# Compare the files
66+
return compare(buildspec, expected_buildspec)
67+
68+
69+
def normalize(contents: str) -> list[str]:
70+
"""Convert string of file contents to list of its non-empty lines"""
71+
return [line.strip() for line in contents.splitlines() if line.strip()]
72+
73+
74+
def compare(buildspec: list[str], expected_buildspec: list[str]) -> int:
75+
"""Compare the lines in the two files directly.
76+
77+
Early return when an unexpected difference is found. If the lengths
78+
mismatch, but the first safe_index_max lines are the same, print
79+
the missing/extra lines.
80+
81+
Returns
82+
-------
83+
int
84+
0 if the generated dockerfile matches the expected output, or non-zero otherwise.
85+
"""
86+
safe_index_max = min(len(buildspec), len(expected_buildspec))
87+
for index in range(safe_index_max):
88+
if buildspec[index] != expected_buildspec[index]:
89+
# Log error
90+
log_err("Mismatch found:")
91+
# Log diff
92+
log_diff(buildspec[index], expected_buildspec[index])
93+
return 1
94+
if safe_index_max < len(expected_buildspec):
95+
log_err("Mismatch found: result is missing trailing lines")
96+
log_diff("", "\n".join(expected_buildspec[safe_index_max:]))
97+
return 1
98+
if safe_index_max < len(buildspec):
99+
log_err("Mismatch found: result has extra trailing lines")
100+
log_diff("\n".join(buildspec[safe_index_max:]), "")
101+
return 1
102+
return 0
103+
104+
105+
if __name__ == "__main__":
106+
raise SystemExit(main())
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
#syntax=docker/dockerfile:1.10
3+
FROM oraclelinux:9
4+
5+
# Install core tools
6+
RUN dnf -y install which wget tar git
7+
8+
# Install compiler and make
9+
RUN dnf -y install gcc make
10+
11+
# Download and unzip interpreter
12+
RUN <<EOF
13+
wget https://www.python.org/ftp/python/3.14.0/Python-3.14.0.tgz
14+
tar -xf Python-3.14.0.tgz
15+
EOF
16+
17+
# Install necessary libraries to build the interpreter
18+
# From: https://devguide.python.org/getting-started/setup-building/
19+
RUN dnf install \
20+
gcc-c++ gdb lzma glibc-devel libstdc++-devel openssl-devel \
21+
readline-devel zlib-devel libzstd-devel libffi-devel bzip2-devel \
22+
xz-devel sqlite sqlite-devel sqlite-libs libuuid-devel gdbm-libs \
23+
perf expat expat-devel mpdecimal python3-pip
24+
25+
# Build interpreter and create venv
26+
RUN <<EOF
27+
cd Python-3.14.0
28+
./configure --with-pydebug
29+
make -s -j $(nproc)
30+
./python -m venv /deps
31+
EOF
32+
33+
# Clone code to rebuild
34+
RUN <<EOF
35+
mkdir src
36+
cd src
37+
git clone https://github.com/tkem/cachetools .
38+
git checkout --force ca7508fd56103a1b6d6f17c8e93e36c60b44ca25
39+
EOF
40+
41+
WORKDIR /src
42+
43+
# Install build and the build backends
44+
RUN <<EOF
45+
/deps/bin/pip install "setuptools==80.9.0" && /deps/bin/pip install "wheel"
46+
/deps/bin/pip install build
47+
EOF
48+
49+
# Run the build
50+
RUN source /deps/bin/activate && python -m build --wheel -n

tests/integration/cases/pypi_cachetools/test.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,17 @@ steps:
3030
kind: default_build_spec
3131
result: output/buildspec/pypi/cachetools/macaron.buildspec
3232
expected: expected_default.buildspec
33+
- name: Generate the buildspec
34+
kind: gen-build-spec
35+
options:
36+
command_args:
37+
- -purl
38+
- pkg:pypi/[email protected]
39+
- --output-format
40+
- dockerfile
41+
- name: Compare Dockerfile.
42+
kind: compare
43+
options:
44+
kind: dockerfile_build_spec
45+
result: output/buildspec/pypi/cachetools/dockerfile.buildspec
46+
expected: expected_dockerfile.buildspec
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
#syntax=docker/dockerfile:1.10
3+
FROM oraclelinux:9
4+
5+
# Install core tools
6+
RUN dnf -y install which wget tar git
7+
8+
# Install compiler and make
9+
RUN dnf -y install gcc make
10+
11+
# Download and unzip interpreter
12+
RUN <<EOF
13+
wget https://www.python.org/ftp/python/3.14.0/Python-3.14.0.tgz
14+
tar -xf Python-3.14.0.tgz
15+
EOF
16+
17+
# Install necessary libraries to build the interpreter
18+
# From: https://devguide.python.org/getting-started/setup-building/
19+
RUN dnf install \
20+
gcc-c++ gdb lzma glibc-devel libstdc++-devel openssl-devel \
21+
readline-devel zlib-devel libzstd-devel libffi-devel bzip2-devel \
22+
xz-devel sqlite sqlite-devel sqlite-libs libuuid-devel gdbm-libs \
23+
perf expat expat-devel mpdecimal python3-pip
24+
25+
# Build interpreter and create venv
26+
RUN <<EOF
27+
cd Python-3.14.0
28+
./configure --with-pydebug
29+
make -s -j $(nproc)
30+
./python -m venv /deps
31+
EOF
32+
33+
# Clone code to rebuild
34+
RUN <<EOF
35+
mkdir src
36+
cd src
37+
git clone https://github.com/executablebooks/markdown-it-py .
38+
git checkout --force c62983f1554124391b47170180e6c62df4d476ca
39+
EOF
40+
41+
WORKDIR /src
42+
43+
# Install build and the build backends
44+
RUN <<EOF
45+
/deps/bin/pip install "flit==3.12.0" && /deps/bin/pip install "flit_core<4,>=3.4"
46+
/deps/bin/pip install build
47+
EOF
48+
49+
# Run the build
50+
RUN source /deps/bin/activate && pip install flit && if test -f "flit.ini"; then python -m flit.tomlify; fi && flit build

tests/integration/cases/pypi_markdown-it-py/test.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,17 @@ steps:
2727
kind: default_build_spec
2828
result: output/buildspec/pypi/markdown-it-py/macaron.buildspec
2929
expected: expected_default.buildspec
30+
- name: Generate the buildspec
31+
kind: gen-build-spec
32+
options:
33+
command_args:
34+
- -purl
35+
- pkg:pypi/[email protected]
36+
- --output-format
37+
- dockerfile
38+
- name: Compare Dockerfile
39+
kind: compare
40+
options:
41+
kind: dockerfile_build_spec
42+
result: output/buildspec/pypi/markdown-it-py/dockerfile.buildspec
43+
expected: expected_dockerfile.buildspec
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
#syntax=docker/dockerfile:1.10
3+
FROM oraclelinux:9
4+
5+
# Install core tools
6+
RUN dnf -y install which wget tar git
7+
8+
# Install compiler and make
9+
RUN dnf -y install gcc make
10+
11+
# Download and unzip interpreter
12+
RUN <<EOF
13+
wget https://www.python.org/ftp/python/3.14.0/Python-3.14.0.tgz
14+
tar -xf Python-3.14.0.tgz
15+
EOF
16+
17+
# Install necessary libraries to build the interpreter
18+
# From: https://devguide.python.org/getting-started/setup-building/
19+
RUN dnf install \
20+
gcc-c++ gdb lzma glibc-devel libstdc++-devel openssl-devel \
21+
readline-devel zlib-devel libzstd-devel libffi-devel bzip2-devel \
22+
xz-devel sqlite sqlite-devel sqlite-libs libuuid-devel gdbm-libs \
23+
perf expat expat-devel mpdecimal python3-pip
24+
25+
# Build interpreter and create venv
26+
RUN <<EOF
27+
cd Python-3.14.0
28+
./configure --with-pydebug
29+
make -s -j $(nproc)
30+
./python -m venv /deps
31+
EOF
32+
33+
# Clone code to rebuild
34+
RUN <<EOF
35+
mkdir src
36+
cd src
37+
git clone https://github.com/beeware/toga .
38+
git checkout --force ef1912b0a1b5c07793f9aa372409f5b9d36f2604
39+
EOF
40+
41+
WORKDIR /src
42+
43+
# Install build and the build backends
44+
RUN <<EOF
45+
/deps/bin/pip install "setuptools==80.3.1" && /deps/bin/pip install "setuptools_dynamic_dependencies==1.0.0" && /deps/bin/pip install "setuptools_scm==8.3.1"
46+
/deps/bin/pip install build
47+
EOF
48+
49+
# Run the build
50+
RUN source /deps/bin/activate && python -m build --wheel -n

tests/integration/cases/pypi_toga/test.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,17 @@ steps:
3434
kind: default_build_spec
3535
result: output/buildspec/pypi/toga/macaron.buildspec
3636
expected: expected_default.buildspec
37+
- name: Generate the buildspec
38+
kind: gen-build-spec
39+
options:
40+
command_args:
41+
- -purl
42+
- pkg:pypi/[email protected]
43+
- --output-format
44+
- dockerfile
45+
- name: Compare Dockerfile
46+
kind: compare
47+
options:
48+
kind: dockerfile_build_spec
49+
result: output/buildspec/pypi/toga/dockerfile.buildspec
50+
expected: expected_dockerfile.buildspec

tests/integration/run.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ def configure_logging(verbose: bool) -> None:
8282
"find_source": ["tests", "find_source", "compare_source_reports.py"],
8383
"rc_build_spec": ["tests", "build_spec_generator", "reproducible_central", "compare_rc_build_spec.py"],
8484
"default_build_spec": ["tests", "build_spec_generator", "common_spec", "compare_default_buildspec.py"],
85+
"dockerfile_build_spec": ["tests", "build_spec_generator", "dockerfile", "compare_dockerfile_buildspec.py"],
8586
}
8687

8788
VALIDATE_SCHEMA_SCRIPTS: dict[str, Sequence[str]] = {

0 commit comments

Comments
 (0)