Skip to content

Commit 2b155bd

Browse files
committed
build: added create_homebrew_formula script
1 parent 368d28a commit 2b155bd

3 files changed

Lines changed: 261 additions & 2 deletions

File tree

Jenkinsfile

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,7 +877,13 @@ pipeline {
877877
}
878878
}
879879
stage('GitHub Release'){
880-
agent any
880+
agent{
881+
docker{
882+
image 'ghcr.io/astral-sh/uv:debian'
883+
label 'docker && linux'
884+
args "--label=purpose=ci --label \"JOB_NAME=\$JOB_NAME\" --label \"absoluteUrl=${currentBuild.absoluteUrl}\" --label \"BUILD_NUMBER=${currentBuild.number}\" --mount source=uv_python_cache_dir,target=/tmp/uvpython"
885+
}
886+
}
881887
when{
882888
beforeInput true
883889
beforeAgent true
@@ -930,8 +936,9 @@ pipeline {
930936
unstash 'PYTHON_PACKAGES'
931937
def releaseData = readJSON text: createReleaseResponse.content
932938
findFiles(glob: 'dist/*').each{
939+
def outputURL = "${releaseData.upload_url.replace('{?name,label}', '')}?name=${it.name}"
933940
def uploadResponse = httpRequest(
934-
url: "${releaseData.upload_url.replace('{?name,label}', '')}?name=${it.name}",
941+
url: "${outputURL}",
935942
httpMode: 'POST',
936943
uploadFile: it.path,
937944
customHeaders: [[name: 'Authorization', value: "token ${GITHUB_TOKEN}"]],
@@ -942,7 +949,17 @@ pipeline {
942949
} else {
943950
error "Failed to upload file: ${uploadResponse.status} - ${uploadResponse.content}"
944951
}
952+
if(it.name.contains(".tar.gz") {
953+
sh(
954+
label: 'Creating homebrew formula',
955+
script: """uv export --format pylock.toml --no-dev > ${WORKSPACE_TMP}/pylock.toml
956+
mkdir -p dist/homebrew_formula/
957+
uv run contrib/create_homebrew_formula.py ${it.path} ${WORKSPACE_TMP}/pylock.toml ${outputURL} > dist/homebrew_formula/tripwire.rb
958+
"""
959+
)
960+
}
945961
}
962+
archiveArtifacts artifacts: 'dist/homebrew_formula/*'
946963
}
947964
}
948965
}

contrib/create_homebrew_formula.py

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
# /// script
2+
# dependencies = [
3+
# "Jinja2",
4+
# ]
5+
# ///
6+
import argparse
7+
import enum
8+
import hashlib
9+
import os
10+
import sys
11+
import tarfile
12+
import tomllib
13+
from dataclasses import dataclass, field, asdict
14+
from typing import TypedDict, List
15+
16+
import jinja2
17+
18+
TEMPLATE_FILE = os.path.join(os.path.dirname(__file__),"tripwire_formula.rb.jinja2")
19+
20+
class GitInfo(TypedDict):
21+
head: str
22+
branch: str
23+
24+
class DependencyType(enum.Enum):
25+
BUILD = 'build'
26+
INSTALL = 'install'
27+
TEST = 'test'
28+
29+
@dataclass
30+
class Dependency:
31+
dependency: str
32+
dependency_type: DependencyType = DependencyType.INSTALL
33+
34+
@dataclass
35+
class Resource:
36+
name: str
37+
url: str
38+
sha256: str
39+
40+
@dataclass
41+
class FormulaInfo:
42+
name: str
43+
desc : str
44+
homepage : str
45+
url : str
46+
sha256 : str
47+
license : str
48+
git_info : GitInfo
49+
resources : List[Resource] = field(default_factory=list)
50+
depends_on: List[Dependency] = field(default_factory=list)
51+
52+
53+
def read_pkg_info_fp(file_pointer):
54+
data = {
55+
"name": None,
56+
"version": None,
57+
"license": None,
58+
"summary": None,
59+
"project_url": None
60+
}
61+
for line in file_pointer.read().decode("utf-8").split("\n"):
62+
match line.split():
63+
case ["Name:", name]:
64+
data["name"] = name
65+
66+
case ["Version:", version]:
67+
data["version"] = version
68+
69+
case ["License-Expression:", license_type]:
70+
data["license"] = license_type
71+
72+
case["Summary:", *values]:
73+
data["summary"] = " ".join(values)
74+
75+
case["Project-URL:", "project,", url]:
76+
data["project_url"] = url
77+
return data
78+
79+
def get_package_info(tarball_location):
80+
metadata = {
81+
"sha256": None
82+
}
83+
84+
with open(tarball_location, 'rb', buffering=0) as f:
85+
metadata['sha256'] = hashlib.file_digest(f, "sha256").hexdigest()
86+
87+
with tarfile.open(tarball_location, "r:gz") as tar:
88+
for member in tar.getmembers():
89+
location, name = os.path.split(member.name)
90+
if member.isdir():
91+
continue
92+
93+
# PKG-INFO is found one level deep in something like uiucprescon_tripwire-0.3.8
94+
if len(location.split(os.sep)) > 1:
95+
continue
96+
if name != "PKG-INFO":
97+
continue
98+
with tar.extractfile(member) as f:
99+
return {**metadata, **read_pkg_info_fp(f)}
100+
101+
raise ValueError("no PKG-INFO found")
102+
103+
def get_lock_file_packages(lockfile_path):
104+
packages = {}
105+
with open(lockfile_path, "rb") as f:
106+
data = tomllib.load(f)
107+
108+
for package in data['packages']:
109+
110+
if 'marker' in package:
111+
if "sys_platform == 'win32'" in package['marker']:
112+
continue
113+
if "sys_platform == 'linux'" in package['marker']:
114+
continue
115+
116+
if "directory" in package:
117+
continue
118+
119+
packages[package['name']] = {
120+
k: v for k, v in package.items() if k not in ("name", "wheels")
121+
}
122+
return packages
123+
124+
def render_formula(data) -> str:
125+
with open(TEMPLATE_FILE) as f:
126+
template = jinja2.Template(
127+
f.read(),
128+
)
129+
template.globals["DependencyType"] = DependencyType
130+
return template.render(**asdict(data))
131+
132+
def get_args():
133+
parser = argparse.ArgumentParser()
134+
135+
parser.add_argument(
136+
"sdist",
137+
help="path to tar.gz sdist file. "
138+
"For example: uiucprescon_tripwire-0.3.8.tar.gz"
139+
)
140+
141+
parser.add_argument(
142+
"lockfile",
143+
help="path to pylock.toml lockfile. For example pylock.toml",
144+
)
145+
146+
parser.add_argument("url", help="url to sdist package stored on internet")
147+
148+
return parser
149+
150+
def validate_args(args):
151+
issues = []
152+
153+
if not os.path.isfile(args.sdist):
154+
issues.append(f"sdist does not exist")
155+
156+
if not os.path.isfile(args.lockfile):
157+
issues.append(f"lockfile does not exist")
158+
159+
return issues
160+
161+
def main():
162+
args = get_args().parse_args()
163+
164+
if issues := validate_args(args):
165+
sys.stdout.flush()
166+
for issue in issues:
167+
print(issue, file=sys.stderr)
168+
exit(1)
169+
170+
info = get_package_info(args.sdist)
171+
lockfile_packages = get_lock_file_packages(args.lockfile)
172+
173+
print(
174+
render_formula(
175+
FormulaInfo(
176+
name="Tripwire",
177+
desc=info['summary'],
178+
homepage=info['project_url'],
179+
url=args.url,
180+
sha256=info['sha256'],
181+
license=info['license'],
182+
git_info={
183+
"head": "https://github.com/UIUCLibrary/tripwire.git",
184+
"branch": "main"
185+
},
186+
depends_on=[
187+
Dependency("conan", DependencyType.BUILD),
188+
Dependency("python@3.14"),
189+
],
190+
resources=sorted(
191+
[
192+
Resource(
193+
name=pkg_name,
194+
url=pkg_data['sdist']['url'],
195+
sha256=pkg_data['sdist']['hashes']['sha256']
196+
) for pkg_name, pkg_data in lockfile_packages.items()
197+
],
198+
key=lambda r: r.name
199+
)
200+
)
201+
)
202+
)
203+
204+
if __name__ == '__main__':
205+
main()

contrib/tripwire_formula.rb.jinja2

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# typed: true
2+
# frozen_string_literal: true
3+
4+
class Tripwire < Formula
5+
include Language::Python::Virtualenv
6+
7+
desc "{{ desc }}"
8+
homepage "{{ homepage }}"
9+
url "{{ url }}"
10+
sha256 "{{ sha256 }}"
11+
license "{{ license }}"
12+
head "{{ git_info['head'] }}", branch: "{{ git_info['branch'] }}"
13+
{%- if depends_on %}
14+
{% for depend in depends_on %}
15+
depends_on "{{ depend['dependency'] }}"{% if depend['dependency_type'] == DependencyType.BUILD %} => :build{% elif depend['dependency_type'] == DependencyType.TEST %}=> :test{% endif -%}
16+
{%- endfor -%}
17+
{% endif %}
18+
{%- if resources %}
19+
{% for resource in resources %}
20+
resource "{{ resource.name }}" do
21+
url "{{ resource.url }}"
22+
sha256 "{{ resource.sha256 }}"
23+
end
24+
{% endfor -%}
25+
{% else %}
26+
{% endif %}
27+
def install
28+
ENV["CONAN_HOME"] = buildpath
29+
system "conan", "profile", "detect"
30+
virtualenv_install_with_resources
31+
end
32+
33+
test do
34+
system bin/"tripwire", "--help"
35+
system bin/"tripwire", "--version"
36+
end
37+
end

0 commit comments

Comments
 (0)