Skip to content

Commit 62a78e7

Browse files
committed
Merge branch 'onnx' into vnnlib
2 parents 20ef6e9 + d068839 commit 62a78e7

File tree

86 files changed

+2110
-591
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+2110
-591
lines changed

.github/actions/lint/action.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
name: lint
2+
description: Run Ruff format check and lint
3+
runs:
4+
using: composite
5+
steps:
6+
- name: Ruff format check
7+
uses: astral-sh/ruff-action@v3
8+
with:
9+
args: format --check .
10+
- name: Ruff lint
11+
uses: astral-sh/ruff-action@v3
12+
with:
13+
args: check .
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
name: setup-python-and-install
2+
description: Set up Python and optionally install packages
3+
inputs:
4+
python-version:
5+
description: Python version to set up
6+
required: true
7+
packages:
8+
description: Space-separated pip packages to install
9+
required: false
10+
default: ""
11+
runs:
12+
using: composite
13+
steps:
14+
- name: Set up Python
15+
uses: actions/setup-python@v6
16+
with:
17+
python-version: ${{ inputs.python-version }}
18+
- name: Install packages
19+
if: ${{ inputs.packages != '' }}
20+
shell: bash
21+
run: |
22+
python -m pip install --upgrade pip
23+
pip install ${{ inputs.packages }}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: write-license
2+
description: Write LICENSE secret into a gurobi.lic file and output its path
3+
inputs:
4+
license:
5+
description: The contents of the license file
6+
required: true
7+
outputs:
8+
grb_license_file:
9+
description: Path to the written license file
10+
value: ${{ steps.write.outputs.grb_license_file }}
11+
runs:
12+
using: composite
13+
steps:
14+
- id: write
15+
shell: bash
16+
env:
17+
LICENSE: ${{ inputs.license }}
18+
run: |
19+
echo "$LICENSE" > "$PWD/gurobi.lic"
20+
echo "grb_license_file=$PWD/gurobi.lic" >> "$GITHUB_OUTPUT"

.github/hooks/license_embedded.py

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,97 @@
11
import json
22
import sys
3+
from datetime import datetime
34
from pathlib import Path
45

56
expected_license = Path("copyright.txt").read_text().rstrip()
67

78

9+
def _allowed_first_line(line: str, base_first_line: str) -> bool:
10+
"""
11+
Allow first line to either be:
12+
- a single current year (YYYY), or
13+
- a range START-YYYY where START is in [2023, YYYY] and YYYY is the current year.
14+
15+
The rest of the text (before and after the year section) must match the base template.
16+
"""
17+
import re
18+
19+
m = re.search(r"\b(\d{4})\b", base_first_line)
20+
if not m:
21+
# If no year in base, require exact match
22+
return line == base_first_line
23+
24+
pre = base_first_line[: m.start()]
25+
post = base_first_line[m.end() :]
26+
27+
if not (line.startswith(pre) and line.endswith(post)):
28+
return False
29+
30+
middle = line[len(pre) : len(line) - len(post)]
31+
current_year = datetime.utcnow().year
32+
33+
# Accept single current year
34+
if re.fullmatch(r"\d{4}", middle):
35+
return int(middle) == current_year
36+
37+
# Accept range START-CURRENT_YEAR
38+
m2 = re.fullmatch(r"(\d{4})-(\d{4})", middle)
39+
if not m2:
40+
return False
41+
42+
start, end = int(m2.group(1)), int(m2.group(2))
43+
if end != current_year:
44+
return False
45+
if start < 2023 or start > end:
46+
return False
47+
return True
48+
49+
50+
def license_header_matches(text: str) -> bool:
51+
base_lines = expected_license.splitlines()
52+
lines = text.splitlines()
53+
if len(lines) < len(base_lines):
54+
return False
55+
56+
# Compare first line with flexible year
57+
if not _allowed_first_line(lines[0], base_lines[0]):
58+
return False
59+
60+
# Compare remaining lines exactly
61+
for i in range(1, len(base_lines)):
62+
if lines[i] != base_lines[i]:
63+
return False
64+
65+
return True
66+
67+
868
def python_check(content: str, cur_file: str) -> str:
9-
if not content.startswith(expected_license):
10-
return f"'{cur_file}' did not start with copyright.txt content"
11-
else:
12-
return ""
69+
if not license_header_matches(content):
70+
y = datetime.utcnow().year
71+
return (
72+
f"'{cur_file}' did not start with copyright.txt content. "
73+
f"First line must use either '{y}' or 'START-{y}' with START>=2023."
74+
)
75+
return ""
1376

1477

1578
def notebook_check(content: str, cur_file: str) -> str:
1679
j = json.loads(content)
17-
code = 0
1880
try:
19-
if not j["metadata"]["license"]["full_text"] == expected_license:
20-
code = 1
81+
full_text = j["metadata"]["license"]["full_text"]
2182
except KeyError:
22-
code = 1
23-
24-
if code == 1:
2583
return (
2684
f"'{cur_file}' does not have the copyright.txt content as metadata. "
2785
f"This should go into metadata.license.full_text"
2886
)
29-
else:
30-
return ""
87+
88+
if not isinstance(full_text, str) or not license_header_matches(full_text):
89+
y = datetime.utcnow().year
90+
return (
91+
f"'{cur_file}' license metadata does not match expected text. "
92+
f"First line must use either '{y}' or 'START-{y}' with START>=2023."
93+
)
94+
return ""
3195

3296

3397
def check_file(cur_file: str) -> str:

.github/scripts/upload_anaconda.sh

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
#!/bin/bash
1+
#!/usr/bin/env bash
22

3-
set -e
3+
set -euo pipefail
44
set -x
55

6-
export PATH=$CONDA/bin:$PATH
7-
conda create -n upload -y python=3.10
8-
source activate upload
9-
conda install -y anaconda-client
10-
11-
anaconda -t $ANACONDA_STAGING_TOKEN upload --force -u gurobi-machinelearning-wheels-staging dist/artifact/*
6+
# Install anaconda-client via pip and upload artifacts without requiring conda
7+
python -m pip install --upgrade anaconda-client
8+
anaconda -t "$ANACONDA_STAGING_TOKEN" upload --force -u gurobi-machinelearning-wheels-staging dist/*

.github/workflows/PR_license.yml

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
# label added. Any changes to the PR in need of a retriggering of the workflow require the removal and re-adding of the
44
# "safe to test" label.
55
#
6-
# Since a subset of tests was already run in the nolicense workflow, this workflow runs skips the following tests:
7-
# - Lint with flake8.
6+
# Since a subset of checks already ran in the nolicense workflow, this workflow skips linting here.
87

98
name: build and test PR, license required
109

@@ -15,47 +14,74 @@ on:
1514
permissions:
1615
contents: read
1716

17+
concurrency:
18+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
19+
cancel-in-progress: true
20+
1821
jobs:
19-
build_pr_license:
22+
tests:
2023
runs-on: ubuntu-latest
2124
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
25+
timeout-minutes: 45
2226
strategy:
27+
fail-fast: false
2328
matrix:
24-
python-version: ["3.9", "3.10", "3.11", "3.12"]
25-
gurobi-version: ["gurobi10", "gurobi11","gurobi12"]
29+
python-version: ["3.10", "3.11", "3.12", "3.13"]
30+
gurobi-version: ["gurobi11","gurobi12","gurobi13"]
2631
exclude:
27-
- python-version: "3.12"
28-
gurobi-version: "gurobi10"
32+
- python-version: "3.13"
33+
gurobi-version: "gurobi11"
34+
steps:
35+
- uses: actions/checkout@v5
36+
with:
37+
ref: ${{ github.event.pull_request.head.sha }}
38+
- name: Setup Python
39+
uses: ./.github/actions/setup-python-and-install
40+
with:
41+
python-version: ${{ matrix.python-version }}
42+
- name: Setup uv
43+
uses: astral-sh/setup-uv@v3
44+
- id: write-license
45+
name: Write license
46+
uses: ./.github/actions/write-license
47+
with:
48+
license: ${{ secrets.LICENSE }}
49+
- name: Test with tox (uv)
50+
env:
51+
GRB_LICENSE_FILE: ${{ steps.write-license.outputs.grb_license_file }}
52+
GUROBI_VERSION: ${{ matrix.gurobi-version }}
53+
run: |
54+
uvx tox
55+
56+
examples:
57+
runs-on: ubuntu-latest
58+
if: contains(github.event.pull_request.labels.*.name, 'safe to test')
59+
needs: tests
60+
timeout-minutes: 45
61+
strategy:
62+
fail-fast: false
63+
matrix:
64+
python-version: ["3.12"]
65+
gurobi-version: ["gurobi11","gurobi12","gurobi13"]
2966
steps:
30-
- uses: actions/checkout@v5
31-
with:
32-
ref: ${{ github.event.pull_request.head.sha }}
33-
- name: Set up Python ${{ matrix.python-version }}
34-
uses: actions/setup-python@v6
35-
with:
36-
python-version: ${{ matrix.python-version }}
37-
- name: Install dependencies
38-
run: |
39-
python -m pip install --upgrade pip
40-
pip install pytest tox tox-gh-actions
41-
- shell: bash
42-
id: write-license
43-
env:
44-
LICENSE: ${{ secrets.LICENSE }}
45-
run: |
46-
echo "$LICENSE" > $PWD/gurobi.lic
47-
echo "grb_license_file=$PWD/gurobi.lic" >> $GITHUB_OUTPUT
48-
- name: Test with tox
49-
run: |
50-
tox
51-
env:
52-
GRB_LICENSE_FILE: ${{ steps.write-license.outputs.grb_license_file }}
53-
GUROBI_VERSION: ${{ matrix.gurobi-version }}
54-
- name: Run examples with Gurobi 11
55-
if: ${{ (matrix.gurobi-version == 'gurobi11') && (matrix.python-version == '3.12') }}
56-
env:
57-
GRB_LICENSE_FILE: ${{ steps.write-license.outputs.grb_license_file }}
58-
GUROBI_VERSION: ${{ matrix.gurobi-version }}
59-
run: |
60-
python_version_no_dot=$(echo "${{ matrix.python-version }}" | tr -d '.')
61-
tox -e py${python_version_no_dot}-examples-${{matrix.gurobi-version}}
67+
- uses: actions/checkout@v5
68+
with:
69+
ref: ${{ github.event.pull_request.head.sha }}
70+
- name: Setup Python
71+
uses: ./.github/actions/setup-python-and-install
72+
with:
73+
python-version: ${{ matrix.python-version }}
74+
- name: Setup uv
75+
uses: astral-sh/setup-uv@v3
76+
- id: write-license
77+
name: Write license
78+
uses: ./.github/actions/write-license
79+
with:
80+
license: ${{ secrets.LICENSE }}
81+
- name: Run examples (uv)
82+
env:
83+
GRB_LICENSE_FILE: ${{ steps.write-license.outputs.grb_license_file }}
84+
GUROBI_VERSION: ${{ matrix.gurobi-version }}
85+
run: |
86+
python_version_no_dot=$(echo "${{ matrix.python-version }}" | tr -d '.')
87+
uvx tox -e py${python_version_no_dot}-examples-${{ matrix.gurobi-version }}

.github/workflows/PR_nolicense.yml

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,41 @@ on:
1313
permissions:
1414
contents: read
1515

16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
18+
cancel-in-progress: true
19+
1620
jobs:
17-
build_pr_nolicense:
21+
lint:
1822
runs-on: ubuntu-latest
23+
timeout-minutes: 10
24+
steps:
25+
- uses: actions/checkout@v5
26+
- name: Lint
27+
uses: ./.github/actions/lint
28+
29+
test:
30+
needs: lint
31+
runs-on: ubuntu-latest
32+
timeout-minutes: 45
1933
strategy:
34+
fail-fast: false
2035
matrix:
21-
python-version: ["3.9", "3.10", "3.11", "3.12"]
22-
gurobi-version: ["gurobi11","gurobi12"]
23-
36+
python-version: ["3.10", "3.11", "3.12", "3.13"]
37+
gurobi-version: ["gurobi11","gurobi12","gurobi13"]
38+
exclude:
39+
- python-version: "3.13"
40+
gurobi-version: "gurobi11"
2441
steps:
25-
- uses: actions/checkout@v5
26-
- name: Set up Python ${{ matrix.python-version }}
27-
uses: actions/setup-python@v6
28-
with:
29-
python-version: ${{ matrix.python-version }}
30-
- name: Install dependencies
31-
run: |
32-
python -m pip install --upgrade pip
33-
pip install flake8 pytest tox tox-gh-actions
34-
- name: Lint with flake8
35-
run: |
36-
# stop the build if there are Python syntax errors or undefined names
37-
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
38-
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
39-
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
40-
- name: Test with tox, excluding the examples target
41-
run: |
42-
tox
43-
env:
44-
GUROBI_VERSION: ${{ matrix.gurobi-version }}
42+
- uses: actions/checkout@v5
43+
- name: Setup Python
44+
uses: ./.github/actions/setup-python-and-install
45+
with:
46+
python-version: ${{ matrix.python-version }}
47+
- name: Setup uv
48+
uses: astral-sh/setup-uv@v3
49+
- name: Test with tox (uv), excluding the examples target
50+
env:
51+
GUROBI_VERSION: ${{ matrix.gurobi-version }}
52+
run: |
53+
uvx tox

0 commit comments

Comments
 (0)