SoarGroup / Soar

Soar, a general cognitive architecture for systems that exhibit intelligent behavior.
http://soar.eecs.umich.edu
Other
322 stars 70 forks source link

Add cibuildwheel support #448

Closed ShadowJonathan closed 1 month ago

ShadowJonathan commented 1 month ago

This PR adds cibuildwheel support for python wheels: allowing for CI-powered reproducible python builds into self-contained wheels.

(Python wheels are distribution packages containing everything a python library needs to run, including shared libraries. They're tagged with compatibility markers (OS, Architecture, and for linux: GLIB version), so that pip can download the correct variant from a package index like https://pypi.org)

This is achieved with enscons, which puppets scons to achieve the right builds and environments.

This names the output library as soar-sml, and renames Python_sml_ClientInterface.py to __init__.py, so that import soar_sml will have the same effect as import Python_sml_ClientInterface.

As this then becomes a pip-installable library, sys.path.append is not required anymore, import soar_sml will do the trick from anywhere on the system.


TODOs


My goal with this is to make Soar ready to be packaged for https://pypi.org distribution (likely in a future PR, with proper discussion), and to then create a library that will make accessing soar even more pythonic. (Name pending, also, soar was taken, *shakes fist*)

I hope that both of these efforts will lower the barrier for entry and experimentation with Soar, especially as SML allows anyone to create a "body" for Soar agents to "interact" with the world.

ShadowJonathan commented 1 month ago

(Converting to draft while I try to figure out Windows builds)

garfieldnate commented 1 month ago

This is very exciting! Thank you so much for looking into this. I would love for users to be able to simply pip install soar-cog-arch==9.6.3 to get Soar (seriously, "snakes on a robot" took the soar name?! 🤣).

For future work (it's a bigger change), we may want this to be usable for general users that want to run VisualSoar, the debugger, etc. from the launch scripts. We would need to include the scripts/jars for running these into the wheel, and we would need to notify the user of where Soar was installed (previous point). Right now the final Soar distribution is built using a separate project: https://github.com/SoarGroup/Release-Support.

Regarding more Pythonic access for Soar, there is a popular (in our tiny community) project that does something similar called pysoarlib. I have a PR open for making it pip-installable: https://github.com/amininger/pysoarlib/pull/7. I actually ended up forking it for our current internal project because I wanted to make a lot of changes to it. I don't want to discourage you from working on a new Python library, as a fresh take would probably be very nice, but I wanted to let you know that it's there. The design pattern with SoarClient using AgentConnector implementations and exposing only the input, output and init events has been especially useful.

ShadowJonathan commented 1 month ago

I would like the package version to match Soar's version

Already intending to, so that soar-sml can have its versioning decoupled, and simply define a range of soar versions for which it supports its python/sml interface.

Or maybe it's not necessary after the wheel is installed?

Not necessary, as Soar gets built and statically linked against the SML interface library (or at the very least, included in the wheel), meaning that its essentially bundling a distribution of Soar with the wheel itself as well.

Was there a technical reason you needed to change it, or was it just that the name is ugly?

Well, multiple reasons;

So part technical reason and social/cleanliness reason.

There could possibly be an extra package which installs that Python_sml_ClientInterface.py at site-packages root and just does from soar_raw import *, maybe call it soar-compat (installable via soar-raw[compat]), but I'm really against having it there by default.

we may want this to be usable for general users that want to run VisualSoar, the debugger, etc. from the launch scripts

I... would have to take a closer look at that, at the moment I'm really just focusing on getting the SML part up and ready. I don't have any thoughts on this at the moment, and the decision on wether to bundle the VisualSoar parts (if in includes java; no), making it easy to make VisualSoar/debugger connectable (probably), or something else, is something I don't have enough info for.

This library is just intended to start the soar kernel, load agent code, and setup the IO link via python. Debugging and whatnot should then be pluggable similar to how someone who usually runs Soar would expect it to work, imo.

For visualsoar or the debugger to connect, the programmer would have to first fire off python with a script that loads the soar agent (with its connector), and only then they could connect to it, methinks. This would miss out the connector part of debugging, which would need to be done on the python side in the IDE's native debugger.

(pysoarlib) [...] but I wanted to let you know that it's there.

I saw, though thanks for mentioning it for if I hadn't :)

I'm going to take a look at it for inspiration, but largely the reason I want to make an independent library is because I then both learn the internals of SML while thinking of a pythonic interface, hopefully with that fresh perspective not being bogged down by rigidity to conform to something similar to Soar's internals, etc.

Taking a look again, the sub-classable connector interface does strike a fancy; I think I'd like more parts of that to be as modular, such as entire input or output trees, "interpreters", so that data gets transformed into WM elements efficiently, and such interpreters/trees can be modularised and shared around, iterated.

(plus having nice formatted value trees on repr() and the likes)

That library design looks poll-based instead of push-based; I want to play around with that as well, where instead of the WM being polled for every update, the programmer can opt to instead explicitly push data at "off" intervals (decoupled from the decision cycle). But I have to think how such a library design would work.

One nice thing about decoupling this from the "raw" library is that I can do alpha/beta versioning while I experiment with this :)

Edit: An additional thought; I can make it work with asyncio, instead of requiring synchronous code, which would then help introduce tons of libraries which do asynchronous IO off the device (such as driving motors, or APIs)

garfieldnate commented 1 month ago

the wheel would have to package a python file "at root"

What do you mean by this? Do you mean that you plan for the import to be from something more nested, like from soargroup.soar import soar_raw or something?

BTW I think calling it soar_sml instead of soar_raw would suffice for clarity; the SML API is as raw as it gets from the Python side. Then for your pythonic wrapper you'd be free to be as creative as you want, from the mundane "soar_pythonic" to the more fun "glider" or "eagle" (or whatever else soars).

I think we can punt the question of more general usage down the road for now. Having any form of pip-installable Soar would already be very cool. This is also related to the point where you say Not necessary, as Soar gets built and statically linked against the SML interface library; a general installation of Soar has SML bindings for Python, Java, Tcl, C++ and C#. It's a lot to manage.

the programmer can opt to instead explicitly push data at "off" intervals

A nice role-reversal! Give highest-level control to the external application instead of to Soar. This programming pattern is less common for Soar because it is more complex, but I would love to see it done. I could be mistaken, but I think you'd want to create the kernel in the current thread using sml::Kernel::CreateKernelInCurrentThread, and you'll need to take note of the usage comment here: https://github.com/SoarGroup/Soar/blob/development/Core/ClientSML/src/sml_ClientKernel.h#L318. You have to call CheckForIncomingCommands() periodically if you want the kernel to be reactive to any other clients (such as the debugger). (If I recall correctly this doesn't apply to the client in the thread that created the kernel, just other clients remotely connecting to it).

I think your ideas for a Pythonic Soar package sound really cool, so I'm excited to see what you come up with!

ShadowJonathan commented 1 month ago
Felt a bit inspired and started drawing up the different ways that soar and the environment could asynchronously run alongside eachother, apologies for the digression. (Click to open) I thought about how `soar-sml` would do asynchronous IO for a second, and I figured there'd at least be three configurations possible; Here's a simple loop; Soar (grey) runs one decision cycle, and then waits before the output (red) has been completed on the connector, then waits before the input (blue) is completed on the connector, and then runs another cycle. ![EB12B0FB-B6D5-4AE1-A6E3-55CD2CB2E1E5](https://github.com/SoarGroup/Soar/assets/22740616/1582846b-bcb3-43a4-8d94-b60bc1c71b11) However, due to asyncio, it is possible to say "Hey, run the output code together with the input code, and only wait for the input to complete"; ![1E7BDBDC-2160-4F49-A33F-5A899DE97D11](https://github.com/SoarGroup/Soar/assets/22740616/12ed246f-6145-4c6e-90f2-7f117430c086) This would have the input and output code fight for the state of the world, but this may be possible in some scenarios (or some sub-connectors), and a programmer might want to enable this for optimisation purposes. Then there's a final method, which runs the input collector every time, and then feeds soar the last result (encircled), while also asynchronously running the output; ![5390790D-5278-4F05-8666-DBCA7507C4CA](https://github.com/SoarGroup/Soar/assets/22740616/78983c8d-17a6-4993-8474-6e5446702d56) Again, this would have implications, but could be something the programmer wants to weigh off. All of these have tradeoffs, but supporting all of them *is* possible, and its good to provide options.
ShadowJonathan commented 1 month ago

What do you mean by this? Do you mean that you plan for the import to be from something more nested, like from soargroup.soar import soar_raw or something?

Currently, the import would be import soar_raw, soar_raw would be installed as a directory under python's site-packages folder, and putting anything but a namespace directory there (or installing to someone else's namespace directory) is frowned upon.

BTW I think calling it soar_sml instead of soar_raw would suffice for clarity

That's fair, then I think I'll indeed swap it around for that (making soar-raw into soar-sml; I'll update this PR with that at some point), and find a good name for my pythonic package :)

A general installation of Soar has SML bindings for Python, Java, Tcl, C++ and C#. It's a lot to manage.

To be honest, I think pivoting to packaging the python binding for soar to a wheel file for users to install would be more user-friendly in the long-term, since instructing them to manually pivot $PATH to that directory is awkward at best, and fragile and non-portable at worst.

(wrt CreateKernelInCurrentThread)

I'll have to see what is most optimal, what I'm seeing here is that doing CreateKernelInCurrentThread basically defers a lot of housekeeping to the developer, right? If so, that'd have more control (and could play nicely with asynchronous IO), but would be a bit more complexity. I'll play around with that later :)

garfieldnate commented 1 month ago

All sounds good :) One last note on concurrency: core Soar does not support it. It is not thread-safe. The SML client library uses locking to ensure that only one command can get sent at a time, and on the kernel side there is a queue for receiving commands over the network socket (as when the debugger connects).

So there are a couple safety measures in place, but if you're not careful you can run into issues like #430. This is also related to the actions one might take in a handler; when control returns to Soar after a handler is complete, it doesn't do any checking to make sure that, e.g. the agent list hasn't changed. Unfortunately these edge cases aren't well-documented, but if you stick to actions that were definitely intended and designed for, such as creating WME's on the input link in the input handler and reading command WME's on the output link in the output handler, everything will work fine.

moschmdt commented 1 month ago

Felt a bit inspired and started drawing up the different ways that soar and the environment could asynchronously run alongside eachother, apologies for the digression. (Click to open) I thought about how soar-sml would do asynchronous IO for a second, and I figured there'd at least be three configurations possible;

Here's a simple loop; Soar (grey) runs one decision cycle, and then waits before the output (red) has been completed on the connector, then waits before the input (blue) is completed on the connector, and then runs another cycle.

EB12B0FB-B6D5-4AE1-A6E3-55CD2CB2E1E5

However, due to asyncio, it is possible to say "Hey, run the output code together with the input code, and only wait for the input to complete";

1E7BDBDC-2160-4F49-A33F-5A899DE97D11

This would have the input and output code fight for the state of the world, but this may be possible in some scenarios (or some sub-connectors), and a programmer might want to enable this for optimisation purposes.

Then there's a final method, which runs the input collector every time, and then feeds soar the last result (encircled), while also asynchronously running the output;

5390790D-5278-4F05-8666-DBCA7507C4CA

Again, this would have implications, but could be something the programmer wants to weigh off.

All of these have tradeoffs, but supporting all of them is possible, and its good to provide options.

O

A little bit off topic: I am currently working on a Soar ROS2 integration and I implemented something similar to your first idea but I instantly push the elements to a consumer thread via queues so Soar remains responsive for output link wmes. For the input I read all queues and attach the data to the input link.

I did not publish the code (cop) yet.

So far this approach works great and I did not experience any weird behaviour. I hope this helps!

ShadowJonathan commented 1 month ago

CI builds properly now, so there are a few outstanding tasks, and some notes:

Notes:

scijones commented 1 month ago

You've given us a lot here. We need to review a few of these things (I haven't caught up on all the stuff that's happened here), but I like this so far. Thanks!

You say "Installing this would instantly recover all compatibility with all the existing projects, and make them properly portable as a result :)" "so just say 'alright' to that if I should do so in this PR"

Alright.

"I've uploaded the current build artifacts to pypi.org/project/soar-sml, this is to prevent someone else nicking and squatting it (I've had that happen a few times before)" --Thanks for that, too!

ShadowJonathan commented 1 month ago

Alright, I added soar-compat and uploaded this small library to pypi as well, together with a new version of build artifacts on an alpha version.

It is now possible to do pip install "soar-sml[compat]", and receive full and simple backwards compatibility with all existing scripts for soar :)

garfieldnate commented 1 month ago

Just tag me with a organisation or maintainer username on pypi that I can transfer ownership of the pypi project to, this is just a safety measure.

I created a garfieldnate account and a SoarGroup organization on pypi today; the organization has to be approved by someone before it exists, so I'll let you know when that comes through.

PyPy does "compile", but it doesn't seem to do it correctly...

Most of the actual running code is going to be compiled C++, and I'm not worried about the speed of the wrapper code. Do you think I should be? Soar does a lot of work, and I would hope that by comparison a simple method call in Python would be no big deal.

We should maybe also decide on how many past versions we want to upload to pypi

I think starting with 9.6.2 is fine. Going back and building earlier versions of Soar is a big pain.

Building for 32-bit windows has also been disabled

That's fine, we don't support it anymore.

Windows wheels also currently have a quirk where they contain both Soar.lib and Soar.dll

Definitely only need the latter, but I wouldn't say it's a show-stopper for now.

Python 3.12 isn't currently being built for.

How does this work? The shard library that Python loads only works with 3.12, which is the version of Python that we build with. This would mean that the wheel wouldn't work at all, since it can only be used with versions of Python that the lib won't work with, but you said it's working fine, and I don't understand how.

The current pypi upload and setup does NOT package source installs

I would have thought it were complete magic if this worked for everyone :D It takes a bit of work to set up the environment to build Soar. I think it's fine to go without this.

ShadowJonathan commented 1 month ago

Most of the actual running code is going to be compiled C++, and I'm not worried about the speed of the wrapper code. Do you think I should be? Soar does a lot of work, and I would hope that by comparison a simple method call in Python would be no big deal.

When dealing with real-time data streams, speed might be a factor in shifting that data around (think the raw bytes of a visual interface).

That, or other miscellaneous operations; Python is single-threaded, any thread simply preemptively shares time on a single lock with any other, so speed of code is important when there is priority and deadlines.

Soar can run without the lock, but asynchronous Python code needing to fetch, update, and shift data around for Soar to feed on might have a huge boost by using PyPy.

Python 3.12 isn't currently being built for.

How does this work?

this is due to a limitation with the build system, enscons, though as I said in review; I'm very tempted to fork it and add a simple few fixes which make it work for Python 3.12; there's nothing on soar's side preventing compilation, just an incompatibility of the build system.

garfieldnate commented 1 month ago

Two general questions I thought of:

ShadowJonathan commented 1 month ago

Does this make the build take significantly longer? Should we maybe only do the cibuildwheel stuff once a week and on any tags or something?

The entire build takes about 15 minutes now. I can recommend just running cibuildwheel on tags, and/or also adding a step which automatically uploads them on pypi on any tagging. (There needs to be a step that checks if the version in pyproject.toml matches the tag though, or else it'll maybe upload wheels of the wrong version)

How much more does this couple us with SCons, which we've been considering moving away from for a while?

Not much, this specific approach (enscons) is a way for the python build system to adapt itself to scons. Any other build system could be used, a more common one (like cmake), and there'll be more options for adapting the python build system against whatever option you'll choose. So in essence: It wouldn't couple y'all at all more to scons. When changing it around, it would just require a (hopefully less laborious) dance again to make it fit with python, but if in end end its just a script that needs to be ran, there is a build system adapter for it.

ShadowJonathan commented 1 month ago

That, or other miscellaneous operations; Python is single-threaded, any thread simply preemptively shares time on a single lock with any other, so speed of code is important when there is priority and deadlines.

(I should follow-up, which is what I'm currently devising ways to get around this restriction by leveraging and studying python's multiprocessing library, and applying that to the wrapper library I'm thinking of.)

garfieldnate commented 1 month ago

The entire build takes about 15 minutes now. I can recommend just running cibuildwheel on tags, and/or also adding a step which automatically uploads them on pypi on any tagging. (There needs to be a step that checks if the version in pyproject.toml matches the tag though, or else it'll maybe upload wheels of the wrong version)

Ideally we would run the cibuildwheel step whenever something has changed just so that we know we haven't broken it, but the upload to pypi step should obviously only occur when we have something to release.

ShadowJonathan commented 1 month ago

I've just now created a fork of enscons: https://github.com/ShadowJonathan/enscons-soar

The current pyproject.toml file will install from that git repository, locked to a specific commit, to enable reproducibility.

I'm not sure if this is a good setup for the future, but;

Honestly, seeing as how @garfieldnate has stated the intent to move away from Scons in the future, I think that this is fine for now (or at least this PR), a permanent temporary fix, unless it needs to be a little less temporary :)


This fork enables:

garfieldnate commented 1 month ago

Moving away from SCons would be a big effort and is thus only a pipe dream for now 🤦

Are you enscons changes interesting generally so that you could try to upstream them? I often link against PR branches.

ShadowJonathan commented 1 month ago

Possibly, but the state of upstream development is very uncertain, the last substantial commit was in 2022, and with only 3 more commits inbetween then and now updating some documentation, and a single commit last week doing some more minor documentation updates: https://github.com/dholth/enscons/

It'll have to happen async, I think.

ShadowJonathan commented 1 month ago

I've addressed all the conversations and uploaded the current artefacts to pypi under a .dev1 version. I've also added this marker in the source code, to be removed when the final version gets uploaded.

The last piece from my side is to fill out the pyproject.toml file, though of course if there are other concerns, please do let me know :)

garfieldnate commented 1 month ago

Thank you so much for all of this! Do you need any extra information for the pyproject.toml?

ShadowJonathan commented 1 month ago

(For the URLs I can imagine https://soargroup.github.io/ at the very least.

and for the keywords I can imagine "sml", "soar", "cognitive architecture", "cognitive", and "soar-sml")

garfieldnate commented 1 month ago
ShadowJonathan commented 1 month ago

Thanks! That was the last todo item. If this was formatted correctly (and cibuildwheel would pick up on it properly), I think this is now ready to be reviewed for merging.

With the next release, the .devX suffix should be removed and, and the version upped to the tagged version.

Currently it builds on every commit, this includes testing if the code will import a basic hello world. This can be changed to build on every tag only, though I'm not sure if doing that in this PR is necessary, since it can be fixed after the fact.

scijones commented 1 month ago

Depending on what author means, we should have special consideration for John Laird, Allen Newell, and Paul Rosenbloom.

As for maintainers, any scraping of contributions back some amount of time from now seems reasonable. Though, I'd consider the Center for Integrated Cognition at IQM Research Institute as a whole to be the de facto "current active maintainer".

garfieldnate commented 1 month ago
$ cd Soar
$ python3 -m venv venv
$ source venv/bin/activate
$ pip install -e Core/ClientSMLSWIG/Python
$ python
>>> import soar_sml
>>> kernel = soar_sml.Kernel.CreateKernelInNewThread()
>>> agent = kernel.CreateAgent("hello")
>>> agent.ExecuteCommandLine("p s1")
'(S1 ^epmem E1 ^io I1 ^reward-link R1 ^smem L1 ^superstate nil\n       ^type state)\n'

Holy smokes I can't believe this just works! 🪄

ShadowJonathan commented 1 month ago

I'm also not able to pip install anything locally for testing yet; pypi says that Intel Mac is uploaded, but I don't have one for testing.

😬

Ai... that was exactly what I was hoping wouldn't happen. Did you do pip install soar-sml==9.6.2 specifically? Since pip install soar-sml should "just work"

If it doesn't, that means I'll have to yank/delete that version (and then .3 is the next "full" version that'll work on pip), or I'll have to take a look at supplementing that version with more binary distributions (wheels).

Currently, 9.6.2.dev1 is uploaded as a "pre-release", pip foregoes prereleases if it sees a final release, which in this case could get it "stuck". My own local pip seems to not have this problem, I'd recommend running pip install -U pip before trying again, and see if it works then.

Edit: I'm currently testing old versions of pip, and on python3.9, installing pip~=19 seems to have problems picking up on the fact that it is ARM64; try running pip install -U pip first? If that doesn't work, please give me the output of pip debug --verbose, so I can figure out which of the tags it is (trying) to grab.

So I guess I'm just wondering if there's any uncertainty that that part won't work once everything is built and ready to upload there.

As I poked over email: that bit was a mistake on my end.
On pypi, for version 9.6.2, there only exists a wheel (binary distribution) for python 3.8 on Intel (x86) Mac, while for the .dev releases, there exists wheels for a bunch of other platforms and Python versions, which pip will resolve properly.

You can test this by manually doing pip install soar-sml==9.6.2.dev1, if you get any errors, please do forward them to me, together with the OS and Python version you're using.

We can remedy this by just uploading wheels for the other platforms and Python versions for 9.6.2 (which is easily done, though it means the metadata of one wheel is off), or removing/yanking that version and releasing "proper" only for .3, though with the way things are going, I think I'll upload the other wheels for .2 after this PR is finished, so that pip install will work properly in all cases, and there'll be no confusion.

ShadowJonathan commented 1 month ago

One odd "hole" I just found in releases; Python 3.8 for ARM64 isn't built and uploaded.

I remember this being an issue on cibuildwheel, where they don't have access (?) or can't install a (proper) ARM64 installer for python3.8 on macos, which means that wheel cant be built.

garfieldnate commented 1 month ago

pip install soar-sml==9.6.2.dev1 yields this error for me:

ERROR: Could not find a version that satisfies the requirement soar-sml==9.6.2.dev1 (from versions: none)
ERROR: No matching distribution found for soar-sml==9.6.2.dev1

The error looks exactly the same (except the version string) when I do pip install soar-sml==9.6.2 or pip install soar-sml.

FYI I'm on an ARM64 Mac.

Here's the pip debug --verbose output, as requested.

pip version: pip 24.0 from /Users/nathanglenn/dev/workspaces/python_workspace/venv/lib/python3.12/site-packages/pip (python 3.12)
sys.version: 3.12.3 (main, Apr  9 2024, 08:09:14) [Clang 15.0.0 (clang-1500.1.0.2.5)]
sys.executable: /Users/nathanglenn/dev/workspaces/python_workspace/venv/bin/python3.12
sys.getdefaultencoding: utf-8
sys.getfilesystemencoding: utf-8
locale.getpreferredencoding: UTF-8
sys.platform: darwin
sys.implementation:
  name: cpython
'cert' config value: Not specified
REQUESTS_CA_BUNDLE: None
CURL_CA_BUNDLE: None
pip._vendor.certifi.where(): /Users/nathanglenn/dev/workspaces/python_workspace/venv/lib/python3.12/site-packages/pip/_vendor/certifi/cacert.pem
pip._vendor.DEBUNDLED: False
vendored library versions:
  CacheControl==0.13.1
  colorama==0.4.6
  distlib==0.3.8
  distro==1.8.0
  msgpack==1.0.5
  packaging==21.3
  platformdirs==3.8.1
  pyparsing==3.1.0
  pyproject-hooks==1.0.0
  requests==2.31.0
  certifi==2023.07.22
  chardet==5.1.0
  idna==3.4
  urllib3==1.26.17
  rich==13.4.2 (Unable to locate actual module version, using vendor.txt specified version)
  pygments==2.15.1
  typing_extensions==4.7.1 (Unable to locate actual module version, using vendor.txt specified version)
  resolvelib==1.0.1
  setuptools==68.0.0 (Unable to locate actual module version, using vendor.txt specified version)
  six==1.16.0
  tenacity==8.2.2 (Unable to locate actual module version, using vendor.txt specified version)
  tomli==2.0.1
  truststore==0.8.0
  webencodings==0.5.1 (Unable to locate actual module version, using vendor.txt specified version)
Compatible tags: 528
  cp312-cp312-macosx_13_0_arm64
  cp312-cp312-macosx_13_0_universal2
  cp312-cp312-macosx_12_0_arm64
  cp312-cp312-macosx_12_0_universal2
  cp312-cp312-macosx_11_0_arm64
  cp312-cp312-macosx_11_0_universal2
  cp312-cp312-macosx_10_16_universal2
  cp312-cp312-macosx_10_15_universal2
  cp312-cp312-macosx_10_14_universal2
  cp312-cp312-macosx_10_13_universal2
  cp312-cp312-macosx_10_12_universal2
  cp312-cp312-macosx_10_11_universal2
  cp312-cp312-macosx_10_10_universal2
  cp312-cp312-macosx_10_9_universal2
  cp312-cp312-macosx_10_8_universal2
  cp312-cp312-macosx_10_7_universal2
  cp312-cp312-macosx_10_6_universal2
  cp312-cp312-macosx_10_5_universal2
  cp312-cp312-macosx_10_4_universal2
  cp312-abi3-macosx_13_0_arm64
  cp312-abi3-macosx_13_0_universal2
  cp312-abi3-macosx_12_0_arm64
  cp312-abi3-macosx_12_0_universal2
  cp312-abi3-macosx_11_0_arm64
  cp312-abi3-macosx_11_0_universal2
  cp312-abi3-macosx_10_16_universal2
  cp312-abi3-macosx_10_15_universal2
  cp312-abi3-macosx_10_14_universal2
  cp312-abi3-macosx_10_13_universal2
  cp312-abi3-macosx_10_12_universal2
  cp312-abi3-macosx_10_11_universal2
  cp312-abi3-macosx_10_10_universal2
  cp312-abi3-macosx_10_9_universal2
  cp312-abi3-macosx_10_8_universal2
  cp312-abi3-macosx_10_7_universal2
  cp312-abi3-macosx_10_6_universal2
  cp312-abi3-macosx_10_5_universal2
  cp312-abi3-macosx_10_4_universal2
  cp312-none-macosx_13_0_arm64
  cp312-none-macosx_13_0_universal2
  cp312-none-macosx_12_0_arm64
  cp312-none-macosx_12_0_universal2
  cp312-none-macosx_11_0_arm64
  cp312-none-macosx_11_0_universal2
  cp312-none-macosx_10_16_universal2
  cp312-none-macosx_10_15_universal2
  cp312-none-macosx_10_14_universal2
  cp312-none-macosx_10_13_universal2
  cp312-none-macosx_10_12_universal2
  cp312-none-macosx_10_11_universal2
  cp312-none-macosx_10_10_universal2
  cp312-none-macosx_10_9_universal2
  cp312-none-macosx_10_8_universal2
  cp312-none-macosx_10_7_universal2
  cp312-none-macosx_10_6_universal2
  cp312-none-macosx_10_5_universal2
  cp312-none-macosx_10_4_universal2
  cp311-abi3-macosx_13_0_arm64
  cp311-abi3-macosx_13_0_universal2
  cp311-abi3-macosx_12_0_arm64
  cp311-abi3-macosx_12_0_universal2
  cp311-abi3-macosx_11_0_arm64
  cp311-abi3-macosx_11_0_universal2
  cp311-abi3-macosx_10_16_universal2
  cp311-abi3-macosx_10_15_universal2
  cp311-abi3-macosx_10_14_universal2
  cp311-abi3-macosx_10_13_universal2
  cp311-abi3-macosx_10_12_universal2
  cp311-abi3-macosx_10_11_universal2
  cp311-abi3-macosx_10_10_universal2
  cp311-abi3-macosx_10_9_universal2
  cp311-abi3-macosx_10_8_universal2
  cp311-abi3-macosx_10_7_universal2
  cp311-abi3-macosx_10_6_universal2
  cp311-abi3-macosx_10_5_universal2
  cp311-abi3-macosx_10_4_universal2
  cp310-abi3-macosx_13_0_arm64
  cp310-abi3-macosx_13_0_universal2
  cp310-abi3-macosx_12_0_arm64
  cp310-abi3-macosx_12_0_universal2
  cp310-abi3-macosx_11_0_arm64
  cp310-abi3-macosx_11_0_universal2
  cp310-abi3-macosx_10_16_universal2
  cp310-abi3-macosx_10_15_universal2
  cp310-abi3-macosx_10_14_universal2
  cp310-abi3-macosx_10_13_universal2
  cp310-abi3-macosx_10_12_universal2
  cp310-abi3-macosx_10_11_universal2
  cp310-abi3-macosx_10_10_universal2
  cp310-abi3-macosx_10_9_universal2
  cp310-abi3-macosx_10_8_universal2
  cp310-abi3-macosx_10_7_universal2
  cp310-abi3-macosx_10_6_universal2
  cp310-abi3-macosx_10_5_universal2
  cp310-abi3-macosx_10_4_universal2
  cp39-abi3-macosx_13_0_arm64
  cp39-abi3-macosx_13_0_universal2
  cp39-abi3-macosx_12_0_arm64
  cp39-abi3-macosx_12_0_universal2
  cp39-abi3-macosx_11_0_arm64
  cp39-abi3-macosx_11_0_universal2
  cp39-abi3-macosx_10_16_universal2
  cp39-abi3-macosx_10_15_universal2
  cp39-abi3-macosx_10_14_universal2
  cp39-abi3-macosx_10_13_universal2
  cp39-abi3-macosx_10_12_universal2
  cp39-abi3-macosx_10_11_universal2
  cp39-abi3-macosx_10_10_universal2
  cp39-abi3-macosx_10_9_universal2
  cp39-abi3-macosx_10_8_universal2
  cp39-abi3-macosx_10_7_universal2
  cp39-abi3-macosx_10_6_universal2
  cp39-abi3-macosx_10_5_universal2
  cp39-abi3-macosx_10_4_universal2
  cp38-abi3-macosx_13_0_arm64
  cp38-abi3-macosx_13_0_universal2
  cp38-abi3-macosx_12_0_arm64
  cp38-abi3-macosx_12_0_universal2
  cp38-abi3-macosx_11_0_arm64
  cp38-abi3-macosx_11_0_universal2
  cp38-abi3-macosx_10_16_universal2
  cp38-abi3-macosx_10_15_universal2
  cp38-abi3-macosx_10_14_universal2
  cp38-abi3-macosx_10_13_universal2
  cp38-abi3-macosx_10_12_universal2
  cp38-abi3-macosx_10_11_universal2
  cp38-abi3-macosx_10_10_universal2
  cp38-abi3-macosx_10_9_universal2
  cp38-abi3-macosx_10_8_universal2
  cp38-abi3-macosx_10_7_universal2
  cp38-abi3-macosx_10_6_universal2
  cp38-abi3-macosx_10_5_universal2
  cp38-abi3-macosx_10_4_universal2
  cp37-abi3-macosx_13_0_arm64
  cp37-abi3-macosx_13_0_universal2
  cp37-abi3-macosx_12_0_arm64
  cp37-abi3-macosx_12_0_universal2
  cp37-abi3-macosx_11_0_arm64
  cp37-abi3-macosx_11_0_universal2
  cp37-abi3-macosx_10_16_universal2
  cp37-abi3-macosx_10_15_universal2
  cp37-abi3-macosx_10_14_universal2
  cp37-abi3-macosx_10_13_universal2
  cp37-abi3-macosx_10_12_universal2
  cp37-abi3-macosx_10_11_universal2
  cp37-abi3-macosx_10_10_universal2
  cp37-abi3-macosx_10_9_universal2
  cp37-abi3-macosx_10_8_universal2
  cp37-abi3-macosx_10_7_universal2
  cp37-abi3-macosx_10_6_universal2
  cp37-abi3-macosx_10_5_universal2
  cp37-abi3-macosx_10_4_universal2
  cp36-abi3-macosx_13_0_arm64
  cp36-abi3-macosx_13_0_universal2
  cp36-abi3-macosx_12_0_arm64
  cp36-abi3-macosx_12_0_universal2
  cp36-abi3-macosx_11_0_arm64
  cp36-abi3-macosx_11_0_universal2
  cp36-abi3-macosx_10_16_universal2
  cp36-abi3-macosx_10_15_universal2
  cp36-abi3-macosx_10_14_universal2
  cp36-abi3-macosx_10_13_universal2
  cp36-abi3-macosx_10_12_universal2
  cp36-abi3-macosx_10_11_universal2
  cp36-abi3-macosx_10_10_universal2
  cp36-abi3-macosx_10_9_universal2
  cp36-abi3-macosx_10_8_universal2
  cp36-abi3-macosx_10_7_universal2
  cp36-abi3-macosx_10_6_universal2
  cp36-abi3-macosx_10_5_universal2
  cp36-abi3-macosx_10_4_universal2
  cp35-abi3-macosx_13_0_arm64
  cp35-abi3-macosx_13_0_universal2
  cp35-abi3-macosx_12_0_arm64
  cp35-abi3-macosx_12_0_universal2
  cp35-abi3-macosx_11_0_arm64
  cp35-abi3-macosx_11_0_universal2
  cp35-abi3-macosx_10_16_universal2
  cp35-abi3-macosx_10_15_universal2
  cp35-abi3-macosx_10_14_universal2
  cp35-abi3-macosx_10_13_universal2
  cp35-abi3-macosx_10_12_universal2
  cp35-abi3-macosx_10_11_universal2
  cp35-abi3-macosx_10_10_universal2
  cp35-abi3-macosx_10_9_universal2
  cp35-abi3-macosx_10_8_universal2
  cp35-abi3-macosx_10_7_universal2
  cp35-abi3-macosx_10_6_universal2
  cp35-abi3-macosx_10_5_universal2
  cp35-abi3-macosx_10_4_universal2
  cp34-abi3-macosx_13_0_arm64
  cp34-abi3-macosx_13_0_universal2
  cp34-abi3-macosx_12_0_arm64
  cp34-abi3-macosx_12_0_universal2
  cp34-abi3-macosx_11_0_arm64
  cp34-abi3-macosx_11_0_universal2
  cp34-abi3-macosx_10_16_universal2
  cp34-abi3-macosx_10_15_universal2
  cp34-abi3-macosx_10_14_universal2
  cp34-abi3-macosx_10_13_universal2
  cp34-abi3-macosx_10_12_universal2
  cp34-abi3-macosx_10_11_universal2
  cp34-abi3-macosx_10_10_universal2
  cp34-abi3-macosx_10_9_universal2
  cp34-abi3-macosx_10_8_universal2
  cp34-abi3-macosx_10_7_universal2
  cp34-abi3-macosx_10_6_universal2
  cp34-abi3-macosx_10_5_universal2
  cp34-abi3-macosx_10_4_universal2
  cp33-abi3-macosx_13_0_arm64
  cp33-abi3-macosx_13_0_universal2
  cp33-abi3-macosx_12_0_arm64
  cp33-abi3-macosx_12_0_universal2
  cp33-abi3-macosx_11_0_arm64
  cp33-abi3-macosx_11_0_universal2
  cp33-abi3-macosx_10_16_universal2
  cp33-abi3-macosx_10_15_universal2
  cp33-abi3-macosx_10_14_universal2
  cp33-abi3-macosx_10_13_universal2
  cp33-abi3-macosx_10_12_universal2
  cp33-abi3-macosx_10_11_universal2
  cp33-abi3-macosx_10_10_universal2
  cp33-abi3-macosx_10_9_universal2
  cp33-abi3-macosx_10_8_universal2
  cp33-abi3-macosx_10_7_universal2
  cp33-abi3-macosx_10_6_universal2
  cp33-abi3-macosx_10_5_universal2
  cp33-abi3-macosx_10_4_universal2
  cp32-abi3-macosx_13_0_arm64
  cp32-abi3-macosx_13_0_universal2
  cp32-abi3-macosx_12_0_arm64
  cp32-abi3-macosx_12_0_universal2
  cp32-abi3-macosx_11_0_arm64
  cp32-abi3-macosx_11_0_universal2
  cp32-abi3-macosx_10_16_universal2
  cp32-abi3-macosx_10_15_universal2
  cp32-abi3-macosx_10_14_universal2
  cp32-abi3-macosx_10_13_universal2
  cp32-abi3-macosx_10_12_universal2
  cp32-abi3-macosx_10_11_universal2
  cp32-abi3-macosx_10_10_universal2
  cp32-abi3-macosx_10_9_universal2
  cp32-abi3-macosx_10_8_universal2
  cp32-abi3-macosx_10_7_universal2
  cp32-abi3-macosx_10_6_universal2
  cp32-abi3-macosx_10_5_universal2
  cp32-abi3-macosx_10_4_universal2
  py312-none-macosx_13_0_arm64
  py312-none-macosx_13_0_universal2
  py312-none-macosx_12_0_arm64
  py312-none-macosx_12_0_universal2
  py312-none-macosx_11_0_arm64
  py312-none-macosx_11_0_universal2
  py312-none-macosx_10_16_universal2
  py312-none-macosx_10_15_universal2
  py312-none-macosx_10_14_universal2
  py312-none-macosx_10_13_universal2
  py312-none-macosx_10_12_universal2
  py312-none-macosx_10_11_universal2
  py312-none-macosx_10_10_universal2
  py312-none-macosx_10_9_universal2
  py312-none-macosx_10_8_universal2
  py312-none-macosx_10_7_universal2
  py312-none-macosx_10_6_universal2
  py312-none-macosx_10_5_universal2
  py312-none-macosx_10_4_universal2
  py3-none-macosx_13_0_arm64
  py3-none-macosx_13_0_universal2
  py3-none-macosx_12_0_arm64
  py3-none-macosx_12_0_universal2
  py3-none-macosx_11_0_arm64
  py3-none-macosx_11_0_universal2
  py3-none-macosx_10_16_universal2
  py3-none-macosx_10_15_universal2
  py3-none-macosx_10_14_universal2
  py3-none-macosx_10_13_universal2
  py3-none-macosx_10_12_universal2
  py3-none-macosx_10_11_universal2
  py3-none-macosx_10_10_universal2
  py3-none-macosx_10_9_universal2
  py3-none-macosx_10_8_universal2
  py3-none-macosx_10_7_universal2
  py3-none-macosx_10_6_universal2
  py3-none-macosx_10_5_universal2
  py3-none-macosx_10_4_universal2
  py311-none-macosx_13_0_arm64
  py311-none-macosx_13_0_universal2
  py311-none-macosx_12_0_arm64
  py311-none-macosx_12_0_universal2
  py311-none-macosx_11_0_arm64
  py311-none-macosx_11_0_universal2
  py311-none-macosx_10_16_universal2
  py311-none-macosx_10_15_universal2
  py311-none-macosx_10_14_universal2
  py311-none-macosx_10_13_universal2
  py311-none-macosx_10_12_universal2
  py311-none-macosx_10_11_universal2
  py311-none-macosx_10_10_universal2
  py311-none-macosx_10_9_universal2
  py311-none-macosx_10_8_universal2
  py311-none-macosx_10_7_universal2
  py311-none-macosx_10_6_universal2
  py311-none-macosx_10_5_universal2
  py311-none-macosx_10_4_universal2
  py310-none-macosx_13_0_arm64
  py310-none-macosx_13_0_universal2
  py310-none-macosx_12_0_arm64
  py310-none-macosx_12_0_universal2
  py310-none-macosx_11_0_arm64
  py310-none-macosx_11_0_universal2
  py310-none-macosx_10_16_universal2
  py310-none-macosx_10_15_universal2
  py310-none-macosx_10_14_universal2
  py310-none-macosx_10_13_universal2
  py310-none-macosx_10_12_universal2
  py310-none-macosx_10_11_universal2
  py310-none-macosx_10_10_universal2
  py310-none-macosx_10_9_universal2
  py310-none-macosx_10_8_universal2
  py310-none-macosx_10_7_universal2
  py310-none-macosx_10_6_universal2
  py310-none-macosx_10_5_universal2
  py310-none-macosx_10_4_universal2
  py39-none-macosx_13_0_arm64
  py39-none-macosx_13_0_universal2
  py39-none-macosx_12_0_arm64
  py39-none-macosx_12_0_universal2
  py39-none-macosx_11_0_arm64
  py39-none-macosx_11_0_universal2
  py39-none-macosx_10_16_universal2
  py39-none-macosx_10_15_universal2
  py39-none-macosx_10_14_universal2
  py39-none-macosx_10_13_universal2
  py39-none-macosx_10_12_universal2
  py39-none-macosx_10_11_universal2
  py39-none-macosx_10_10_universal2
  py39-none-macosx_10_9_universal2
  py39-none-macosx_10_8_universal2
  py39-none-macosx_10_7_universal2
  py39-none-macosx_10_6_universal2
  py39-none-macosx_10_5_universal2
  py39-none-macosx_10_4_universal2
  py38-none-macosx_13_0_arm64
  py38-none-macosx_13_0_universal2
  py38-none-macosx_12_0_arm64
  py38-none-macosx_12_0_universal2
  py38-none-macosx_11_0_arm64
  py38-none-macosx_11_0_universal2
  py38-none-macosx_10_16_universal2
  py38-none-macosx_10_15_universal2
  py38-none-macosx_10_14_universal2
  py38-none-macosx_10_13_universal2
  py38-none-macosx_10_12_universal2
  py38-none-macosx_10_11_universal2
  py38-none-macosx_10_10_universal2
  py38-none-macosx_10_9_universal2
  py38-none-macosx_10_8_universal2
  py38-none-macosx_10_7_universal2
  py38-none-macosx_10_6_universal2
  py38-none-macosx_10_5_universal2
  py38-none-macosx_10_4_universal2
  py37-none-macosx_13_0_arm64
  py37-none-macosx_13_0_universal2
  py37-none-macosx_12_0_arm64
  py37-none-macosx_12_0_universal2
  py37-none-macosx_11_0_arm64
  py37-none-macosx_11_0_universal2
  py37-none-macosx_10_16_universal2
  py37-none-macosx_10_15_universal2
  py37-none-macosx_10_14_universal2
  py37-none-macosx_10_13_universal2
  py37-none-macosx_10_12_universal2
  py37-none-macosx_10_11_universal2
  py37-none-macosx_10_10_universal2
  py37-none-macosx_10_9_universal2
  py37-none-macosx_10_8_universal2
  py37-none-macosx_10_7_universal2
  py37-none-macosx_10_6_universal2
  py37-none-macosx_10_5_universal2
  py37-none-macosx_10_4_universal2
  py36-none-macosx_13_0_arm64
  py36-none-macosx_13_0_universal2
  py36-none-macosx_12_0_arm64
  py36-none-macosx_12_0_universal2
  py36-none-macosx_11_0_arm64
  py36-none-macosx_11_0_universal2
  py36-none-macosx_10_16_universal2
  py36-none-macosx_10_15_universal2
  py36-none-macosx_10_14_universal2
  py36-none-macosx_10_13_universal2
  py36-none-macosx_10_12_universal2
  py36-none-macosx_10_11_universal2
  py36-none-macosx_10_10_universal2
  py36-none-macosx_10_9_universal2
  py36-none-macosx_10_8_universal2
  py36-none-macosx_10_7_universal2
  py36-none-macosx_10_6_universal2
  py36-none-macosx_10_5_universal2
  py36-none-macosx_10_4_universal2
  py35-none-macosx_13_0_arm64
  py35-none-macosx_13_0_universal2
  py35-none-macosx_12_0_arm64
  py35-none-macosx_12_0_universal2
  py35-none-macosx_11_0_arm64
  py35-none-macosx_11_0_universal2
  py35-none-macosx_10_16_universal2
  py35-none-macosx_10_15_universal2
  py35-none-macosx_10_14_universal2
  py35-none-macosx_10_13_universal2
  py35-none-macosx_10_12_universal2
  py35-none-macosx_10_11_universal2
  py35-none-macosx_10_10_universal2
  py35-none-macosx_10_9_universal2
  py35-none-macosx_10_8_universal2
  py35-none-macosx_10_7_universal2
  py35-none-macosx_10_6_universal2
  py35-none-macosx_10_5_universal2
  py35-none-macosx_10_4_universal2
  py34-none-macosx_13_0_arm64
  py34-none-macosx_13_0_universal2
  py34-none-macosx_12_0_arm64
  py34-none-macosx_12_0_universal2
  py34-none-macosx_11_0_arm64
  py34-none-macosx_11_0_universal2
  py34-none-macosx_10_16_universal2
  py34-none-macosx_10_15_universal2
  py34-none-macosx_10_14_universal2
  py34-none-macosx_10_13_universal2
  py34-none-macosx_10_12_universal2
  py34-none-macosx_10_11_universal2
  py34-none-macosx_10_10_universal2
  py34-none-macosx_10_9_universal2
  py34-none-macosx_10_8_universal2
  py34-none-macosx_10_7_universal2
  py34-none-macosx_10_6_universal2
  py34-none-macosx_10_5_universal2
  py34-none-macosx_10_4_universal2
  py33-none-macosx_13_0_arm64
  py33-none-macosx_13_0_universal2
  py33-none-macosx_12_0_arm64
  py33-none-macosx_12_0_universal2
  py33-none-macosx_11_0_arm64
  py33-none-macosx_11_0_universal2
  py33-none-macosx_10_16_universal2
  py33-none-macosx_10_15_universal2
  py33-none-macosx_10_14_universal2
  py33-none-macosx_10_13_universal2
  py33-none-macosx_10_12_universal2
  py33-none-macosx_10_11_universal2
  py33-none-macosx_10_10_universal2
  py33-none-macosx_10_9_universal2
  py33-none-macosx_10_8_universal2
  py33-none-macosx_10_7_universal2
  py33-none-macosx_10_6_universal2
  py33-none-macosx_10_5_universal2
  py33-none-macosx_10_4_universal2
  py32-none-macosx_13_0_arm64
  py32-none-macosx_13_0_universal2
  py32-none-macosx_12_0_arm64
  py32-none-macosx_12_0_universal2
  py32-none-macosx_11_0_arm64
  py32-none-macosx_11_0_universal2
  py32-none-macosx_10_16_universal2
  py32-none-macosx_10_15_universal2
  py32-none-macosx_10_14_universal2
  py32-none-macosx_10_13_universal2
  py32-none-macosx_10_12_universal2
  py32-none-macosx_10_11_universal2
  py32-none-macosx_10_10_universal2
  py32-none-macosx_10_9_universal2
  py32-none-macosx_10_8_universal2
  py32-none-macosx_10_7_universal2
  py32-none-macosx_10_6_universal2
  py32-none-macosx_10_5_universal2
  py32-none-macosx_10_4_universal2
  py31-none-macosx_13_0_arm64
  py31-none-macosx_13_0_universal2
  py31-none-macosx_12_0_arm64
  py31-none-macosx_12_0_universal2
  py31-none-macosx_11_0_arm64
  py31-none-macosx_11_0_universal2
  py31-none-macosx_10_16_universal2
  py31-none-macosx_10_15_universal2
  py31-none-macosx_10_14_universal2
  py31-none-macosx_10_13_universal2
  py31-none-macosx_10_12_universal2
  py31-none-macosx_10_11_universal2
  py31-none-macosx_10_10_universal2
  py31-none-macosx_10_9_universal2
  py31-none-macosx_10_8_universal2
  py31-none-macosx_10_7_universal2
  py31-none-macosx_10_6_universal2
  py31-none-macosx_10_5_universal2
  py31-none-macosx_10_4_universal2
  py30-none-macosx_13_0_arm64
  py30-none-macosx_13_0_universal2
  py30-none-macosx_12_0_arm64
  py30-none-macosx_12_0_universal2
  py30-none-macosx_11_0_arm64
  py30-none-macosx_11_0_universal2
  py30-none-macosx_10_16_universal2
  py30-none-macosx_10_15_universal2
  py30-none-macosx_10_14_universal2
  py30-none-macosx_10_13_universal2
  py30-none-macosx_10_12_universal2
  py30-none-macosx_10_11_universal2
  py30-none-macosx_10_10_universal2
  py30-none-macosx_10_9_universal2
  py30-none-macosx_10_8_universal2
  py30-none-macosx_10_7_universal2
  py30-none-macosx_10_6_universal2
  py30-none-macosx_10_5_universal2
  py30-none-macosx_10_4_universal2
  cp312-none-any
  py312-none-any
  py3-none-any
  py311-none-any
  py310-none-any
  py39-none-any
  py38-none-any
  py37-none-any
  py36-none-any
  py35-none-any
  py34-none-any
  py33-none-any
  py32-none-any
  py31-none-any
  py30-none-any
garfieldnate commented 1 month ago

Thanks for all of the comments and changes. Everything looks really good!

ShadowJonathan commented 1 month ago

...right, I see what's wrong, you're on MacOS 13, while the builder aims for MacOS 14, and so there are no wheels it can download.

This should be configurable, and I'll attempt to get it down to MacOS 12 or lower, to maximise compatibility with old MacOS versions.

ShadowJonathan commented 1 month ago

@garfieldnate I've fixed the problem with the latest commit, this should now properly "downversion" the built-for mac wheels, so that it tags itself by the minimum mac version it can get downloaded by, and as a result we got Intel Mac compatibility for Mavericks (10.9!), and all Apple Silicon Macs :)

I've uploaded the new build artifacts that tag these correctly under .dev2, I suspect that the next wheels I'll upload will have that dynamic versioning mechanism I talked about, but for now, soar-sml should be installable on (realistically) all Macs :)

(Excluding python 3.8 on Apple Silicon (ARM64) macs, for reasons I poked earlier, and in the file comments)

garfieldnate commented 1 month ago

I tried it just now and it just worked! Tested on my ARM Mac, then x86_64 Ubuntu and Windows 11.

ShadowJonathan commented 1 month ago

The way I'll be doing it (by splitting the jobs) will make CI faster, too.

I'll add a CI job that'll publish the build versions to test.pypi.org, and I'll add a CI job that'll publish them to regular pypi if the CI run gets triggered by a tag push / release.

garfieldnate commented 1 month ago

I'm having some trouble understanding the GitHub UI, but as far as I can tell, the one question I just posted is the last unresolved conversation, correct? And then it's ready to merge, right?

garfieldnate commented 1 month ago

One other question 😅 Do we need to upload soar-compat to test.pypi.org?

ShadowJonathan commented 1 month ago

I'm having some trouble understanding the GitHub UI, but as far as I can tell, the one question I just posted is the last unresolved conversation, correct?

Yes

And then it's ready to merge, right?

If you feel like it is, yes :)

Do we need to upload soar-compat to test.pypi.org?

...Yes, actually, I just tested it, and it can't find soar-compat, so I'll upload it right now ^^;

Edit: done :)

ShadowJonathan commented 1 month ago

Thank you a lot! 💚