Skip to content

Commit ba5a366

Browse files
authored
♻️ refactor: use build replace download and add musllinux_1_2_i686musllinux_1_2_armv7l (#17)
1 parent 9e7f6c2 commit ba5a366

File tree

6 files changed

+394
-53
lines changed

6 files changed

+394
-53
lines changed

.github/workflows/release.yaml

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,65 @@ on:
88
workflow_dispatch:
99

1010
jobs:
11+
test:
12+
runs-on: ${{ matrix.os }}
13+
strategy:
14+
matrix:
15+
os: [ubuntu-latest, windows-latest, macos-latest]
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
- uses: actions/setup-go@v5
20+
with:
21+
go-version: '1.24.4'
22+
23+
- name: Install uv
24+
uses: astral-sh/setup-uv@v5
25+
26+
- name: Build package
27+
run: |
28+
uv venv
29+
uv build
30+
31+
- name: Install package and test
32+
if: matrix.os != 'windows-latest'
33+
run: |
34+
source .venv/bin/activate
35+
uv pip install --no-deps --force-reinstall dist/*.whl
36+
python tests/test_yamlfmt.py
37+
38+
- name: Install package on Windows and test
39+
if: matrix.os == 'windows-latest'
40+
run: |
41+
.venv\Scripts\activate.ps1
42+
$whl = Get-ChildItem dist\*.whl | Select-Object -First 1
43+
uv pip install --no-deps --force-reinstall $whl.FullName
44+
python tests\test_yamlfmt.py
45+
46+
1147
release-build:
1248
runs-on: ${{ matrix.platform.runner }}
1349
strategy:
1450
matrix:
1551
platform:
1652
- {"runner": "ubuntu-latest", "platform_tag": "musllinux_1_2", "arch": "x86_64"}
53+
- {"runner": "ubuntu-latest", "platform_tag": "musllinux_1_2", "arch": "i686"}
54+
- {"runner": "ubuntu-latest", "platform_tag": "musllinux_1_2", "arch": "armv7l"}
1755
- {"runner": "ubuntu-latest", "platform_tag": "musllinux_1_2", "arch": "aarch64"}
1856
- {"runner": "ubuntu-latest", "platform_tag": "manylinux_2_17", "arch": "x86_64"}
1957
- {"runner": "ubuntu-latest", "platform_tag": "manylinux_2_17", "arch": "aarch64"}
2058
- {"runner": "windows-latest", "platform_tag": "win", "arch": "amd64"}
2159
- {"runner": "windows-latest", "platform_tag": "win", "arch": "arm64"}
22-
- {"runner": "macos-latest", "platform_tag": "macosx_10_9", "arch": "x86_64"}
60+
- {"runner": "macos-latest", "platform_tag": "macosx_10_12", "arch": "x86_64"}
2361
- {"runner": "macos-latest", "platform_tag": "macosx_11_0", "arch": "arm64"}
2462
steps:
2563
- name: Checkout
2664
uses: actions/checkout@v4
2765

66+
- uses: actions/setup-go@v5
67+
with:
68+
go-version: '1.24.4'
69+
2870
- name: Install uv
2971
uses: astral-sh/setup-uv@v5
3072

@@ -50,6 +92,11 @@ jobs:
5092
runs-on: ubuntu-latest
5193
steps:
5294
- uses: actions/checkout@v4
95+
96+
- uses: actions/setup-go@v5
97+
with:
98+
go-version: '1.24.4'
99+
53100
- name: Install uv
54101
uses: astral-sh/setup-uv@v5
55102

@@ -75,6 +122,7 @@ jobs:
75122
name: Publish to PyPI
76123
if: startsWith(github.ref, 'refs/tags/')
77124
needs:
125+
- test
78126
- release-build
79127
- sdist
80128
permissions:
@@ -98,6 +146,7 @@ jobs:
98146
name: Publish to GitHub
99147
if: startsWith(github.ref, 'refs/tags/')
100148
needs:
149+
- test
101150
- release-build
102151
permissions:
103152
contents: write

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ wheels/
88

99
# Virtual environments
1010
.venv
11-
src/yamlfmt/_version.py
1211
.python-version
1312
.ruff_cache
13+
14+
# Configuration files
15+
src/yamlfmt/yamlfmt
16+
src/yamlfmt/_version.py

hatch_build.py

Lines changed: 109 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,45 @@
11
from __future__ import annotations
22

33
import os
4+
import platform
45
import re
56
import shutil
6-
import tarfile
7+
import subprocess
78
import tempfile
89
from pathlib import Path
910
from typing import Any
10-
from urllib import request
1111

1212
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
1313

14+
PY_PLATFORM_MAPPING = {
15+
("Linux", "x86_64"): ("musllinux_1_2", "x86_64"),
16+
("Linux", "i686"): ("musllinux_1_2", "i686"),
17+
("Linux", "armv7l"): ("musllinux_1_2", "armv7l"),
18+
("Linux", "aarch64"): ("musllinux_1_2", "aarch64"),
19+
("Darwin", "x86_64"): ("macosx_10_12", "x86_64"),
20+
("Darwin", "arm64"): ("macosx_11_0", "arm64"),
21+
("Windows", "AMD64"): ("win", "amd64"),
22+
("Windows", "ARM64"): ("win", "arm64"),
23+
}
24+
1425
# key 为 pypi 分发的系统和架构组合
1526
BUILD_TARGET = {
16-
("musllinux_1_2", "x86_64"): {"download_file": ("linux", "x86_64")},
17-
("musllinux_1_2", "aarch64"): {"download_file": ("linux", "arm64")},
18-
("manylinux_2_17", "x86_64"): {"download_file": ("linux", "x86_64")},
19-
("manylinux_2_17", "aarch64"): {"download_file": ("linux", "arm64")},
20-
("macosx_10_9", "x86_64"): {"download_file": ("darwin", "x86_64")},
21-
("macosx_11_0", "arm64"): {"download_file": ("darwin", "arm64")},
22-
("win", "amd64"): {"download_file": ("windows", "x86_64")},
23-
("win", "arm64"): {"download_file": ("windows", "arm64")},
27+
("musllinux_1_2", "x86_64"): ("linux", "amd64"),
28+
("musllinux_1_2", "i686"): ("linux", "386"),
29+
("musllinux_1_2", "armv7l"): ("linux", "arm"),
30+
("musllinux_1_2", "aarch64"): ("linux", "arm64"),
31+
("manylinux_2_17", "x86_64"): ("linux", "amd64"),
32+
("manylinux_2_17", "aarch64"): ("linux", "arm64"),
33+
("macosx_10_12", "x86_64"): ("darwin", "amd64"),
34+
("macosx_11_0", "arm64"): ("darwin", "arm64"),
35+
("win", "amd64"): ("windows", "amd64"),
36+
("win", "arm64"): ("windows", "arm64"),
2437
}
2538

2639

2740
class SpecialBuildHook(BuildHookInterface):
2841
BIN_NAME = "yamlfmt"
29-
YAMLFMT_REPO = "https://github.com/google/yamlfmt/releases/download/v{version}/yamlfmt_{version}_{target_os_info}_{target_arch}.tar.gz"
42+
YAMLFMT_REPO = "https://github.com/google/yamlfmt.git"
3043

3144
def __init__(self, *args, **kwargs):
3245
super().__init__(*args, **kwargs)
@@ -37,56 +50,101 @@ def initialize(self, version: str, build_data: dict[str, Any]) -> None:
3750
if self.target_name != "wheel":
3851
return
3952

40-
target_arch = os.environ.get("CIBW_ARCHS", None)
41-
target_os_info = os.environ.get("CIBW_PLATFORM", None)
53+
# 获取系统信息
54+
system_info = get_system_info()
55+
default_os_mapping = (None, None)
4256

43-
assert target_arch is not None, f"CIBW_ARCHS not set see: {BUILD_TARGET}"
44-
assert target_os_info is not None, f"CIBW_PLATFORM not set see: {BUILD_TARGET}"
57+
# 获取目标架构和平台信息
58+
target_arch = os.environ.get("CIBW_ARCHS") or PY_PLATFORM_MAPPING.get(system_info, default_os_mapping)[1]
59+
target_os_info = os.environ.get("CIBW_PLATFORM") or PY_PLATFORM_MAPPING.get(system_info, default_os_mapping)[0]
4560

46-
if (target_os_info, target_arch) not in BUILD_TARGET:
47-
raise ValueError(f"Unsupported target: {target_os_info}, {target_arch}")
61+
# 确保目标架构和平台信息有效
62+
assert target_arch is not None, (
63+
f"CIBW_ARCHS not set and no mapping found in PY_PLATFORM_MAPPING for: {system_info}"
64+
)
65+
assert target_os_info is not None, (
66+
f"CIBW_PLATFORM not set and no mapping found in PY_PLATFORM_MAPPING for: {system_info}"
67+
)
68+
69+
assert (target_os_info, target_arch) in BUILD_TARGET, f"Unsupported target: {target_os_info}, {target_arch}"
4870

4971
# 构建完整的 Wheel 标签
5072
full_wheel_tag = f"py3-none-{target_os_info}_{target_arch}"
5173
build_data["tag"] = full_wheel_tag
5274

53-
# 下载 yamlfmt 可执行文件
54-
tar_gz_file = self.download_yamlfmt(target_os_info, target_arch)
55-
56-
# 解压缩文件
57-
with tarfile.open(tar_gz_file, "r:gz") as tar:
58-
if target_os_info == "win":
59-
# Windows 上的文件名是 yamlfmt.exe
60-
assert f"{self.BIN_NAME}.exe" in tar.getnames()
61-
tar.extract(f"{self.BIN_NAME}.exe", path=self.temp_dir)
62-
# 重命名为 yamlfmt
63-
(self.temp_dir / f"{self.BIN_NAME}.exe").rename(self.temp_dir / self.BIN_NAME)
64-
else:
65-
assert self.BIN_NAME in tar.getnames()
66-
tar.extract(self.BIN_NAME, path=self.temp_dir)
67-
68-
# TODO: 加一个 sum 校验
75+
# 构建 yamlfmt 二进制文件
76+
self.build_yamlfmt(target_os_info, target_arch)
77+
78+
# 将构建好的二进制文件添加到 wheel 中
6979
bin_path = self.temp_dir / self.BIN_NAME
70-
assert bin_path.is_file(), f"{self.BIN_NAME} not found"
71-
build_data["force_include"][f"{bin_path.resolve()}"] = f"yamlfmt/{self.BIN_NAME}"
72-
73-
def download_yamlfmt(self, target_os_info: str, target_arch: str) -> None:
74-
"""Download the yamlfmt binary for the specified OS and architecture."""
75-
download_target = BUILD_TARGET[(target_os_info, target_arch)]["download_file"]
76-
file_path = self.temp_dir / f"{self.BIN_NAME}_{download_target[0]}_{download_target[1]}.tar.gz"
77-
request.urlretrieve(
78-
self.YAMLFMT_REPO.format(
79-
version=re.sub(
80-
r"(?:a|b|rc)\d+|\.post\d+|\.dev\d+$", "", self.metadata.version
81-
), # 去掉版本号中的后缀, alpha/beta/rc/post/dev
82-
target_os_info=download_target[0],
83-
target_arch=download_target[1],
84-
),
85-
file_path,
80+
81+
assert bin_path.is_file(), f"{self.BIN_NAME} not found after build"
82+
build_data["force_include"][str(bin_path.resolve())] = f"yamlfmt/{self.BIN_NAME}"
83+
84+
def build_yamlfmt(self, target_os_info: str, target_arch: str) -> None:
85+
"""Build the yamlfmt binary for the specified OS and architecture."""
86+
# 确认环境安装
87+
for command in ["go", "make", "git"]:
88+
assert shutil.which(command), f"{command} is not installed or not found in PATH"
89+
90+
build_target = BUILD_TARGET[(target_os_info, target_arch)]
91+
92+
# 编译逻辑可以在这里添加
93+
version = re.sub(
94+
r"(?:a|b|rc)\d+|\.post\d+|\.dev\d+$", "", self.metadata.version
95+
) # 去掉版本号中的后缀, alpha/beta/rc/post/dev
96+
97+
# clone repo
98+
subprocess.run(
99+
[
100+
"git",
101+
"clone",
102+
"--depth",
103+
"1",
104+
"--branch",
105+
f"v{version}",
106+
self.YAMLFMT_REPO,
107+
str(self.temp_dir / f"yamlfmt-{version}"),
108+
],
109+
check=True,
86110
)
87-
return file_path
111+
112+
# 编译
113+
env = os.environ.copy()
114+
env.update({"GOOS": build_target[0], "GOARCH": build_target[1]})
115+
if target_arch == "armv7l":
116+
env.update({"GOARM": "7"})
117+
118+
# 检查工作目录是否存在
119+
work_dir = self.temp_dir / f"yamlfmt-{version}"
120+
assert work_dir.exists(), f"Working directory {work_dir} does not exist"
121+
122+
subprocess.run(
123+
["make", "build"],
124+
env=env,
125+
cwd=work_dir,
126+
capture_output=True,
127+
text=True,
128+
check=True,
129+
)
130+
131+
# 检查生成的二进制文件是否存在
132+
bin_path = work_dir / "dist" / self.BIN_NAME
133+
assert bin_path.exists(), f"Binary file {bin_path} was not created"
134+
135+
# 将二进制文件复制到临时目录的根目录,供后续使用
136+
shutil.copy2(bin_path, self.temp_dir / self.BIN_NAME)
88137

89138
def finalize(self, version, build_data, artifact_path):
90139
# 清理临时目录
91-
shutil.rmtree(self.temp_dir)
140+
try:
141+
shutil.rmtree(self.temp_dir)
142+
except (OSError, PermissionError) as e:
143+
print(f"Warning: Failed to remove temp directory {self.temp_dir}: {e}")
92144
super().finalize(version, build_data, artifact_path)
145+
146+
147+
def get_system_info():
148+
system = platform.system()
149+
machine = platform.machine()
150+
return system, machine

tests/README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Test Configuration for yamlfmt
2+
3+
This directory contains tests for the yamlfmt cross-platform functionality.
4+
5+
## Test Files
6+
7+
- `test_yamlfmt.py`: Main test suite that validates yamlfmt functionality across different platforms
8+
9+
## Running Tests
10+
11+
### Local Testing
12+
13+
Run tests locally with:
14+
15+
```bash
16+
python -m pytest tests/ -v
17+
```
18+
19+
Or run the test script directly:
20+
21+
```bash
22+
python tests/test_yamlfmt.py
23+
```
24+
25+
### CI Testing
26+
27+
The tests are automatically run in GitHub Actions across multiple platforms:
28+
29+
- Linux (Ubuntu)
30+
- macOS
31+
- Windows
32+
33+
## Test Coverage
34+
35+
The test suite covers:
36+
37+
1. **Platform Detection**: Verifies the current platform can be detected
38+
2. **Version Output**: Tests that yamlfmt can output version information
39+
3. **Basic Formatting**: Tests basic YAML formatting functionality
40+
4. **Executable Permissions**: Verifies the yamlfmt executable has correct permissions
41+
5. **Help Output**: Tests that yamlfmt can show help information
42+
6. **Module Import**: Tests that the yamlfmt module can be imported correctly
43+
7. **System Information**: Displays system information for debugging
44+
45+
## Adding New Tests
46+
47+
When adding new tests:
48+
49+
1. Follow the existing naming convention (`test_*`)
50+
2. Include appropriate error handling for cross-platform compatibility
51+
3. Add descriptive docstrings
52+
4. Use appropriate assertions for validation

tests/test_input.yaml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Test YAML file for yamlfmt testing
2+
name: test-project
3+
version: 1.0.0
4+
description: A test project for validating yamlfmt functionality
5+
# Dependencies section with poor formatting
6+
dependencies:
7+
- pytest>=6.0
8+
- black>=22.0
9+
- ruff>=0.1.0
10+
# Configuration with inconsistent indentation
11+
config:
12+
debug: true
13+
log_level: info
14+
timeout: 30
15+
max_retries: 3
16+
# List with inconsistent formatting
17+
scripts:
18+
- build
19+
- test
20+
- format
21+
- lint
22+
# Nested structure
23+
database:
24+
host: localhost
25+
port: 5432
26+
credentials:
27+
username: admin
28+
password: secret123
29+
database: test_db
30+
# Array of objects
31+
servers:
32+
- name: web-1
33+
ip: 192.168.1.10
34+
ports:
35+
- 80
36+
- 443
37+
- name: web-2
38+
ip: 192.168.1.11
39+
ports: [8080, 8443]

0 commit comments

Comments
 (0)