-
-
Notifications
You must be signed in to change notification settings - Fork 372
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Override requirements (at a given level) from a requirements file. #1209
base: main
Are you sure you want to change the base?
Conversation
… that case, instead of merging, we override Signed-off-by: Moshe Zadka <moshez@zadka.club>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So - there's a start of a locking implementation here, but there's still a lot of work needed.
I've flagged a bunch of stuff inline that stood out on a first pass - the most notable of which is that the code doesn't run as provided. The invocation of piptools straight up crashes in my testing... and I can't see how it has ever worked.
There's three "bigger picture" issues that need some work as well.
- It seems like there's a bit of a missed opportunity for a simplification. This PR is using pistols to produce a requirements.txt file, which is then being post-processed back into a list of requirements. If we're going to the trouble of building a requirements file for the purposes of locking, it seems foolish to not to just use it as an input to
pip
. - It misses the overlap with the existing
install_app_requirements()
invocation that is already requirements file based. Some platforms (notably, Android, plus the AppImage and System Linux backends) don't use pip to install requirements directly - they write a requirements file based on the requires list provided. - Continuing that theme - there is some non-trivial backend-specific handling of requirements in the Linux, Android and web backends. It's not immediately clear to me that the changes made in this PR will be compatible with that backend handling (and, because the PR doesn't run as provided, I can't test those theories).
- Lastly - the general workflow wasn't obvious to me on first pass. I would have expected a workflow where
create
generated a lock file when requirements were installed (possibly requiring a--lock
argument); the user could then choose to adopt that lock file by adding it to theirpyproject.toml
. I'm not absolutely wedded to that as a UX - but requiring the user to modify thepyproject.toml
to reference arequires_lock
file that doesn't yet exist in order to generate a lock file with that name was definitely contrary to what I would have expected.
text=True, | ||
capture_output=True, | ||
check=True, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So... did you run this? Briefcase's run
wrapper doesn't support capture_output
, and doesn't need text
, and this crashes for me on a briefcase build -r --relock
.
If all we're looking to do is capture all of the output, capture_output()
would be a better match; or, is there a reason to do the whole dance through stdout at all? Why not just output directly to the lock file (possibly with a -U
flag to force an upgrade of any existing locked requirements)?
if cmd not in {"run", "build", "create", "update"}: | ||
continue | ||
rest[-1].pop("relock", None) | ||
return actions |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what this is in aid of... the point of these tests is to confirm that the right arguments are being passed in to each stage. Post-processing the results to remove one of the arguments... doesn't make any sense to me.
|
||
Use | ||
``briefcase update -r --relock`` | ||
to recompute the lock file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is... certainly a formatting choice. Is there a reason for the free verse style here? :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Conversation about general requirements file support moved to #1275]
@@ -46,7 +46,7 @@ def verify_app_tools(self, app): | |||
|
|||
# Override all the body methods of a UpdateCommand | |||
# with versions that we can use to track actions performed. | |||
def install_app_requirements(self, app, test_mode): | |||
def install_app_requirements(self, app, test_mode, relock=False): | |||
self.actions.append(("requirements", app.app_name, test_mode)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also be tracking the provided argument in the list of actions.
@@ -118,7 +118,7 @@ def generate_app_template(self, app): | |||
def install_app_support_package(self, app): | |||
self.actions.append(("support", app.app_name)) | |||
|
|||
def install_app_requirements(self, app, test_mode): | |||
def install_app_requirements(self, app, test_mode, relock=False): | |||
self.actions.append(("requirements", app.app_name, test_mode)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also be tracking the provided argument in the list of actions.
@@ -504,9 +546,7 @@ def install_app_requirements(self, app: BaseConfig, test_mode: bool): | |||
:param app: The config object for the app | |||
:param test_mode: Should the test requirements be installed? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a missing docstring here.
return requires | ||
|
||
def install_app_requirements( | ||
self, app: BaseConfig, test_mode: bool, relock: bool = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
relock
shouldn't have a default value here; we're in a position to be explicit whenever it's used.
parser.add_argument( | ||
"--relock", | ||
action="store_true", | ||
help=f"Recalculate locked requirements for the app{context_label}", | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why "relock" rather than simply "lock"? "Relock" implies that it was locked before, which isn't necessarily the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the way it's defined in the PR at present, it is a relock, because the initial create call always passes relock=True
, automatically creating the lock file if the setting exists. This is part of my issue with this PR, as you need to specify a lock file that doesn't exist to get the initial invocation to work as you'd expect. So, "re" part of the relocking is present, but it won't always be clear how you get to the relock in the first place.
If the setting is also exposed to the create
command, then it would definitely make sense for the option to be --lock
. It might even make sense regardless, describing "locking" as an imperative action that is part of a build.
That seems a bit awkward. Instead of requiring the user to pick a filename, why not just auto-generate the filename based on the platform and format names? Then pyproject.toml doesn't need to be edited; instead, we have the following scenarios:
I don't have a clear idea of the file's name or location, but maybe we could store it under a |
So, one reason is that people can be... unreasonably opinionated about the names they use for their lock files :-)
I do like this as a workflow. My hesitation is around the slightly opaque user experience - "I've added foobar==1.2.3 to my requirements, but it's not getting installed" becomes even harder to diagnose. This is always going to be the case when lock files are involved, but when the lock file is completely implied, there's no evidence in the config file that directs the user's attention to the fact that locking is being performed.
This is where Opinions (tm) get involved :-) The only conventions I've seen around requirements are either (1) in the project root, or (2) in a |
I think changing the requirements list should always invalidate an existing lock file. We could store the original (merged but unlocked) requirements file next to the lock file, and compare it with the current configuration. This also touches on the automatic update idea discussed at #807 (comment).
If we do decide that the lock file is an internal implementation detail, I think it should be stored in a directory whose name makes that clear. |
Ah - the overlap with #807 changes things significantly. I was considering the lock file as something you could (concievably, although you're not likely to) write by hand, in which case manual specification and customisation options make more sense. Having the lock autogenerated on any change to requirements is another matter entirely.
Agreed; although the lock file is something that would be checked into version control, so we need to be careful not to make it too hidden; and we need to make sure that it any "#807 state" information that feeds the lock file invalidation process is also storable in version control. |
In that case, instead of merging, we override
Fixes: #476
PR Checklist: