Skip to content

Commit da28595

Browse files
Merge pull request #68 from traversaro/copystaging
Add job to copy packages built in 2025 from robostack-staging to robostack-noetic and robostack-humble
2 parents 3f5f9a6 + 0138a54 commit da28595

File tree

4 files changed

+1076
-49
lines changed

4 files changed

+1076
-49
lines changed

Diff for: .github/workflows/copy-staging-packages.yml

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Copy staging packages
2+
3+
on:
4+
workflow_dispatch:
5+
schedule:
6+
- cron: "42 * * * *"
7+
8+
jobs:
9+
copy-packages-noetic:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- uses: prefix-dev/setup-pixi@v0.8.1
14+
- env:
15+
ANACONDA_API_TOKEN: ${{ secrets.ROBOSTACK_NOETIC_ANACONDA_API_TOKEN }}
16+
run: |
17+
python copy-to-distro-specific-channels.py noetic 2025-01-01
18+
shell: pixi run bash -e {0}
19+
20+
copy-packages-humble:
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
- uses: prefix-dev/setup-pixi@v0.8.1
25+
- env:
26+
ANACONDA_API_TOKEN: ${{ secrets.ROBOSTACK_HUMBLE_ANACONDA_API_TOKEN }}
27+
run: |
28+
python copy-to-distro-specific-channels.py humble 2025-01-01
29+
shell: pixi run bash -e {0}

Diff for: copy-to-distro-specific-channel.py

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import requests
2+
import subprocess
3+
import argparse
4+
import datetime
5+
6+
# Configuration
7+
BASE_URL = "https://conda.anaconda.org"
8+
SOURCE_CHANNEL = "robostack-staging"
9+
PLATFORMS = ["linux-64", "linux-aarch64", "win-64", "osx-64", "osx-arm64", "noarch"]
10+
11+
def fetch_repodata(channel, platform):
12+
"""
13+
Fetch the repodata.json file from a given channel and platform.
14+
"""
15+
url = f"{BASE_URL}/{channel}/{platform}/repodata.json"
16+
response = requests.get(url)
17+
18+
if response.status_code == 200:
19+
return response.json()
20+
else:
21+
print(f"Error fetching repodata.json from {channel}/{platform}: {response.status_code}")
22+
return None
23+
24+
def upload_package(package_name_simple, package_name, version, build, platform, destination_channel):
25+
"""
26+
Upload a package to the specified Anaconda channel.
27+
"""
28+
try:
29+
# Construct the full package identifier with platform
30+
package_identifier = f"{SOURCE_CHANNEL}/{package_name_simple}/{version}/{platform}/{package_name}"
31+
32+
print(f"Uploading package: {package_name}, version: {version}, build: {build}, platform: {platform}")
33+
command_vec = ["anaconda", "copy", package_identifier, "--to-owner", destination_channel]
34+
subprocess.run(
35+
command_vec,
36+
check=True
37+
)
38+
print(f"Successfully uploaded: {package_name}, version: {version}, build: {build}, platform: {platform}")
39+
except subprocess.CalledProcessError as e:
40+
print(f"Failed to upload {package_name}, version {version}, build {build}, platform {platform}: {e}")
41+
42+
def main():
43+
# Parse command-line arguments
44+
parser = argparse.ArgumentParser(description="Upload packages from robostack-staging to robostack-<distroname>.")
45+
parser.add_argument(
46+
"distro",
47+
type=str,
48+
help="The distro upload (e.g., 'humble')"
49+
)
50+
parser.add_argument(
51+
"cutoff",
52+
type=str,
53+
help="Only package built after this cutoff date are uploaded. The cutoff date is a a string with format YYYY-MM-DD."
54+
)
55+
args = parser.parse_args()
56+
distro = args.distro
57+
58+
destination_channel = f"robostack-{distro}"
59+
60+
# Convert cutoff date to cutoff timestamps
61+
cutoff_timestamp = int(datetime.datetime.strptime(args.cutoff, "%Y-%m-%d").timestamp()*1000.0)
62+
63+
# Fetch repodata.json for each platform from source and destination channels
64+
source_repodata = {}
65+
destination_repodata = {}
66+
for platform in PLATFORMS:
67+
source_repodata[platform] = fetch_repodata(SOURCE_CHANNEL, platform) or {}
68+
destination_repodata[platform] = fetch_repodata(destination_channel, platform) or {}
69+
70+
# Process packages for each platform
71+
for platform in PLATFORMS:
72+
print(f"Processing platform: {platform}")
73+
74+
# Extract packages from source and destination
75+
source_packages = {}
76+
source_packages.update(source_repodata[platform].get("packages", {}))
77+
source_packages.update(source_repodata[platform].get("packages.conda", {}))
78+
destination_packages = {}
79+
destination_packages.update(destination_repodata[platform].get("packages", {}))
80+
destination_packages.update(destination_repodata[platform].get("packages.conda", {}))
81+
destination_keys = {
82+
(pkg_name, pkg_data["version"], pkg_data["build"])
83+
for pkg_name, pkg_data in destination_packages.items()
84+
}
85+
86+
# Filter packages that belong to the given distro
87+
# and are newer then the specified cutoff date
88+
prefix = 'ros-'+distro
89+
filtered_packages = {
90+
pkg_name: pkg_data
91+
for pkg_name, pkg_data in source_packages.items()
92+
# This should cover both packages that start with 'ros-<distro>'
93+
# '(ros|ros2)-<distro>-mutex' packages whose build string contains <distro>
94+
if (pkg_name.startswith(prefix) or (pkg_data["name"].endswith("distro-mutex") and distro in pkg_data["build"])) and (pkg_data["timestamp"] >= cutoff_timestamp)
95+
}
96+
97+
print(f"Found {len(filtered_packages)} packages in {SOURCE_CHANNEL}/{platform} that belong to distro {distro}")
98+
99+
# Upload packages that are not already in the destination channel
100+
for pkg_name, pkg_data in filtered_packages.items():
101+
package_name = pkg_name
102+
version = pkg_data["version"]
103+
build = pkg_data["build"]
104+
package_name_simple = pkg_data["name"]
105+
106+
if (package_name, version, build) not in destination_keys:
107+
upload_package(package_name_simple, package_name, version, build, platform, destination_channel)
108+
else:
109+
print(f"Package {package_name}, version {version}, build {build}, platform {platform} already exists in {destination_channel}. Skipping upload.")
110+
111+
if __name__ == "__main__":
112+
main()

0 commit comments

Comments
 (0)