Skip to content

Deploy and Integrate Rich Implementation#755

Merged
freakboy3742 merged 2 commits into
beeware:mainfrom
rmartin16:run-in-wait-bar
Jun 16, 2022
Merged

Deploy and Integrate Rich Implementation#755
freakboy3742 merged 2 commits into
beeware:mainfrom
rmartin16:run-in-wait-bar

Conversation

@rmartin16

@rmartin16 rmartin16 commented Jun 4, 2022

Copy link
Copy Markdown
Member

Follow up PR to the implementation of Rich in #740.

Wide Use of Wait Bar

These changes implement the Wait Bar in many places....it might be argued too many. However, I defend using Wait Bar this prolifically because it creates a more consistent user experience with active feedback for ongoing tasks. While many of the tasks will not last long enough for the pulsing bar to even be visible, the consistent done message after these tasks complete directly confers success to the user....especially when they are considered together with the much more long running tasks.

Allow subprocess.run() Without Redirected IO within Wait Bars

A major limitation of the Wait Bar is wrapping a call to subprocess.run() when IO hasn't been redirected (since Rich cannot accommodate dynamic updates to the screen while other processes are also writing to it). The current workaround is to pipe output back out and print it once the command finishes....but generally speaking the output is most useful in real time; for instance, a long running process with important information presented when it starts.

These changes allow callers to wrap run() calls with a Wait Bar whether they redirect IO or not. This also allows callers to more arbitrarily wrap functions without regard to whether they call run() or not. (Although, care does need to be taken to avoid a subsequent wait bar or progress bar being instantiated since only one can be active at a time.)

If run() detects a wait bar is active and IO is not redirected, then it will simulate subprocess.run() by:

  • Redirecting output
    • stdout will always be piped
    • If the caller has not explicitly piped stderr, then it is redirected to stdout
  • Opening the process with Popen
  • Displaying output in real time using the output streamer
  • Finally, return or raise as subprocess.run() does

Alternatively, if the caller properly redirects stdout and stderr, then subprocess.run() is called normally. Although, using check_output is probably more appropriate at that point.

There are a couple limitations:

  • The run arguments timeout and input are not implemented.
  • The detection that the caller redirected the output is not perfect.
    • The underlying expectation is that callers will simply call run() without specifying stdout/err within a Wait Bar and it works.
    • If callers set stdout/err, then they should ensure it properly redirects IO.
  • A wait bar should not be used to wrap run if the shell command has its own robust output; e.g. building an APK.

Printing Consistency

A lot of impetus for this PR was allowing run in a Wait Bar for starting the iOS simulator....then I started comparing what's shown to a user when the iOS simulator starts versus the Android simulator. Then I got sucked in to a black hole of consistency in general among the different workflows. I ultimately ended up trying to create consistency among all of the workflows and even detailed all of the permutations for instantiating briefcase.

Areas of Interest

  • Printing a message with a prefix
    • I noticed every time a message w/ a prefix was printed, an empty call to Log was done first...
      • So, let's print an empty line any time a prefix is used back up in Log
    • Additionally, I tried to make sure all messages were printed in the context of a message with a prefix
      • In this way, a message with a prefix communicates a high level task/goal and messages printed under it are smaller tasks to achieve that outcome.
  • Starting a simulator
    • While iOS and Android have different semantics, I tried to make the steps similar between the two...they already weren't too dissimilar though.
  • Installing/Upgrading a tool
    • Added a bit more logging structure to tool installation and upgrades.
    • Made their code structure a little more similar to each other.
Briefcase Instantiation Permutations
briefcase new
briefcase new --template

briefcase dev
briefcase dev --app
briefcase dev --update-dependencies
briefcase dev --no-run

briefcase upgrade
briefcase upgrade --list

briefcase create android gradle
briefcase create iOS xcode
briefcase create linux appimage
briefcase create linux appimage --no-docker
briefcase create macOS app
briefcase create macOS xcode
briefcase create windows msi

briefcase update android gradle
briefcase update android gradle --update-dependencies
briefcase update android gradle --update-resources
briefcase update android gradle --update-dependencies --update-resources
briefcase update iOS xcode
briefcase update iOS xcode --update-dependencies
briefcase update iOS xcode --update-resources
briefcase update iOS xcode --update-dependencies --update-resources
briefcase update linux appimage
briefcase update linux appimage --update-dependencies
briefcase update linux appimage --update-resources
briefcase update linux appimage --update-dependencies --update-resources
briefcase update macOS app
briefcase update macOS app --update-dependencies
briefcase update macOS app --update-resources
briefcase update macOS app --update-dependencies --update-resources
briefcase update macOS xcode
briefcase update macOS xcode --update-dependencies
briefcase update macOS xcode --update-resources
briefcase update macOS xcode --update-dependencies --update-resources
briefcase update windows msi
briefcase update windows msi --update-dependencies
briefcase update windows msi --update-resources
briefcase update windows msi --update-dependencies --update-resources

briefcase build android gradle
briefcase build android gradle --update
briefcase build iOS xcode
briefcase build iOS xcode --update
briefcase build linux appimage
briefcase build linux appimage --update
briefcase build linux appimage --no-docker
briefcase build linux appimage --no-docker --update
briefcase build macOS app
briefcase build macOS app --update
briefcase build macOS xcode
briefcase build macOS xcode --update
briefcase build windows msi
briefcase build windows msi --update

briefcase run android gradle
briefcase run android gradle --update
briefcase run iOS xcode
briefcase run iOS xcode --update
briefcase run linux appimage
briefcase run linux appimage --update
briefcase run linux appimage --no-docker
briefcase run linux appimage --no-docker --update
briefcase run macOS app
briefcase run macOS app --update
briefcase run macOS xcode
briefcase run macOS xcode --update
briefcase run windows msi
briefcase run windows msi --update

briefcase package android gradle --packaging-format aab
briefcase package android gradle --packaging-format aab --update
briefcase package iOS xcode --packaging-format ipa
briefcase package iOS xcode --packaging-format ipa --update
briefcase package linux appimage --packaging-format appimage
briefcase package linux appimage --packaging-format appimage --update
briefcase package linux appimage --no-docker --packaging-format appimage
briefcase package linux appimage --no-docker --packaging-format appimage --update
briefcase package macOS app --packaging-format app
briefcase package macOS app --packaging-format app --adhoc-sign
briefcase package macOS app --packaging-format app --no-sign
briefcase package macOS app --packaging-format app --update 
briefcase package macOS app --packaging-format app --update --adhoc-sign
briefcase package macOS app --packaging-format app --update --no-sign
briefcase package macOS app --packaging-format dmg
briefcase package macOS app --packaging-format dmg --no-sign
briefcase package macOS app --packaging-format dmg --adhoc-sign
briefcase package macOS app --packaging-format dmg --update
briefcase package macOS app --packaging-format dmg --update --no-sign
briefcase package macOS app --packaging-format dmg --update --adhoc-sign
briefcase package macOS xcode --packaging-format app
briefcase package macOS xcode --packaging-format app --adhoc-sign
briefcase package macOS xcode --packaging-format app --no-sign
briefcase package macOS xcode --packaging-format app --update 
briefcase package macOS xcode --packaging-format app --update --adhoc-sign
briefcase package macOS xcode --packaging-format app --update --no-sign
briefcase package macOS xcode --packaging-format dmg
briefcase package macOS xcode --packaging-format dmg --no-sign
briefcase package macOS xcode --packaging-format dmg --adhoc-sign
briefcase package macOS xcode --packaging-format dmg --update
briefcase package macOS xcode --packaging-format dmg --update --no-sign
briefcase package macOS xcode --packaging-format dmg --update --adhoc-sign
briefcase package windows msi --packaging-format msi
briefcase package windows msi --packaging-format msi --update

PR Checklist:

  • All new features have been tested
  • All new features have been documented
  • I have read the CONTRIBUTING.md file
  • I will abide by the code of conduct

@rmartin16 rmartin16 force-pushed the run-in-wait-bar branch 3 times, most recently from 7e1400a to 0e97ba6 Compare June 5, 2022 19:16
Comment thread src/briefcase/console.py
@rmartin16 rmartin16 force-pushed the run-in-wait-bar branch 2 times, most recently from 0d04a04 to 1b77f78 Compare June 5, 2022 20:31
Comment thread src/briefcase/integrations/android_sdk.py Outdated
Comment thread src/briefcase/integrations/subprocess.py Outdated
Comment thread src/briefcase/__main__.py
@rmartin16

rmartin16 commented Jun 5, 2022

Copy link
Copy Markdown
Member Author

hmm.....I had initially considered abstracting my attempts to standardize some of the tool management code flow in something like InstallableToolMixIn.....so the presentation of actions on these tools to the user always appears similar. #756 is reinforcing that may actually be worthwhile....at least so people don't have to keep recreating the same code flow :)

@rmartin16 rmartin16 marked this pull request as ready for review June 5, 2022 20:50

@freakboy3742 freakboy3742 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the whole, this looks really good. +1 to:

  • The refactoring of uninstall/upgrade commands
  • The rationalization of the "when do we output a blank line?" question. Using prefix output as a marker for a "new context" is an easy to explain and easy to follow rationale, and it results in a very elegant output 👏
  • The "waitbar safety catch" on subprocess.run()
  • Consistency in simulator startup messaging.

I've flagged a couple of fairly minor tweaks inline; the only thing that I'm on the fence about is some of the increased usage of waitbar.

I'm broadly in support of using waitbar more liberally, and 80% of the changes you've made to that end make perfect sense. Anywhere that involves network access is a no-brainer for a waitbar; anything involving non-trivial I/O operations (e.g., unpacking a potentially large zip file) is in the same category.

However, I question whether it's worth it for some of the smaller operations. The one that really made this obvious was the linuxdeploy chmod check - that's literally a single OS filesystem call. I can't think of any circumstance (short of a catastrophic disk problem) where that call will be anything more than microseconds.

I'm a little more sympathetic to cases where it's a small collection of low impact OS calls - e.g., "installing source" is a check for an existing directory, a mkdir and a copy; bundling them into a single waitbar makes a little more sense, even though each individual operation is unlikely to be slow.

I guess what I'd really like to get to is a similar sort of place that this PR has gotten to with respect to "when should I call info() to clear the console?". If we can determine a clear policy regarding when/how we use a waitbar, then it will be a lot easier to apply that policy consistently in future. My first draft of this is effectively summarizing what I've said above - i.e., Use a waitbar if:

  • There's any network access involved in the operation
  • There's a disk I/O operation involved that may take an undetermined amount of time
  • There's a collection of smaller operations that are thematically linked, each of which is unlikely to take any noticeable duration, but can be gathered together thematically.

If you've got any thoughts/counterarguments on this, I'd love to hear them.

Comment thread src/briefcase/exceptions.py Outdated
Comment thread src/briefcase/integrations/linuxdeploy.py Outdated
Comment thread src/briefcase/integrations/linuxdeploy.py Outdated
Comment thread src/briefcase/integrations/android_sdk.py Outdated
Comment thread src/briefcase/platforms/windows/msi.py Outdated
self.logger.info("Building MSI...", prefix=app.app_name)

try:
self.logger.info()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a theme of consistency - are these logger.info()s still needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually struggled with these empty info() calls.....because at least one of them always results in output to the terminal.

Here's the terminal's final state without the info() calls:

(venv-3.9) PS C:\Users\Russell\tmp\beeware-tutorial\helloworld> briefcase package

[helloworld] Building MSI...
Compiling application manifest... done
helloworld.wxs
helloworld-manifest.wxs
Compiling application installer... done
Linking application installer... done

[helloworld] Packaged windows\Hello World-0.0.1.msi

That print out concerning wxs files is related to installer compilation....but it appears related to manifest compilation.

I talk about this more in my comment.

Comment thread src/briefcase/integrations/subprocess.py Outdated
Comment thread src/briefcase/platforms/macOS/__init__.py Outdated
@rmartin16 rmartin16 force-pushed the run-in-wait-bar branch 3 times, most recently from 9b3bbc5 to d4d9e5d Compare June 8, 2022 18:03
@rmartin16

rmartin16 commented Jun 8, 2022

Copy link
Copy Markdown
Member Author

The more I think about it, the more I realize that "when and for what do i use a wait bar?" is really a sub-question to "what is the policy for communicating with the user?". (Through yet another monologue of my mine :)) I propose some shifts in how logging should work.

A digression first though...
One of the outcomes I've noticed about the Wait Bar is that because it is dynamically kept at the bottom of the terminal even if the awaited process produces output, the Wait Bar message is always the last thing printed regarding a specific task. Now, this is fairly obvious from the semantics of the Wait Bar we've designed....but the final state of the contents of the terminal can create a misleading chronology; insofar as the output from the awaited process appears to have occurred before the process began...or perhaps more so, that the preceding task may have produced that output. This is especially true for Wait Bars (I put in this PR) that don't last very long. (See the Windows MSI packaging example.)

I say this because I think it should have important implications for how the Wait Bar is used. And furthermore, how regular logging occurs. In this PR, I initially just replaced every log that contained ellipsis with a Wait Bar...since that log was apparently indicating that something was about to happen (and it may even take some time). However, with the revelation above about "final state" of a screen for users, I think the better approach is considering when to tell the user about something.

Therefore, I propose:

  • For regular logging, don't tell the user you're about to do something; do it and report success to the user...or log an error if it fails.
  • For Wait Bars, a few scenarios:
    • Wrapping a command with no output (even in its failure mode)
      • Use a Wait Bar without concern
    • Wrapping a command that always has output
      • Always print a regular log first stating what the command will do ending with an ellipsis
      • Then start the command in a Wait Bar
        • It may make sense to leave message="" so it isn't just reiterating the same thing.
        • In the case message="", I think making the Wait Bar 40 characters again would be nice.
        • The Wait Bar implementation would also be updated to print the done_message at the end even if message=""
    • Wrapping a command that could have output
      • Depending on the circumstance, I think it's rational to fallback to either one of the scenarios above.
      • For instance, if the command fails and an Exception is raised, just using a Wait normally Bar is probably fine.
      • Alternatively, if the command fails, some stuff about what happened is printed, and execution continues....I think it'd be better to use the other scenario so when the user scrolls back up to it for review, it would all likely be clearer for them.

Now, as for a broad logging policy:

  • Start significant tasks with a "prefix log"

    • This establishes a "context" to complete a task/outcome and communicate about subsequent "subtasks".
    • In this way, practically all logging is either a "context" or a "subtask".
    • Any subtask should be logged within a context.
    • (Taking this further, contexts are really just subtasks for the overall command.)
  • Logging subtasks within a context

    • Generally, any "meaningful" step should be logged as a subtask.
      • Soo, as for what's meaningful....I guess like goldilocks, find the middle ground of what makes sense to log.
      • If a command could potentially fail the entire context, it should almost certainly be logged as subtask for the user.
    • Use a Progress Bar for an enumerable subtask that's likely to take appreciable time
      • E.g. downloading a file or applying the same function against every item in an iterable.
      • A regular log call (or at least intervening log calls for each iteration of progress) should always proceed a Progress Bar to explain it.
    • Use a Wait Bar for a subtask with indeterminate but likely appreciable length.
      • Follow rules above for how to use the Wait Bar.
    • Otherwise, report the success or failure of a subtask after it runs with a regular log.

Notes:

  • I've tried to boil down the answer to "when should a Wait Bar be used?" to basically be "use a Wait Bar when the user is likely to be waiting for a command to finish". Otherwise, just run the command and then tell the user what happened.
  • So, I'm definitely with you at this point on my backing off of using the Wait Bar so much.

Please let me know what I didn't explain very well. If we like this plan, I still need to implement it in this PR....I can probably do that tomorrow.

@freakboy3742

Copy link
Copy Markdown
Member

The more I think about it, the more I realize that "when and for what do i use a wait bar?" is really a sub-question to "what is the policy for communicating with the user?". (Through yet another monologue of my mine :)) I propose some shifts in how logging should work.

+1 for the issues you're thinking about here - you've definitely got the right concerns in mind.

Therefore, I propose:

  • For regular logging, don't tell the user you're about to do something; do it and report success to the user...or log an error if it fails.

A qualified maybe on this one.

The concern I have is how the log reads when an error occurs. The advantage to telling someone you're about to do something is that when something breaks, the last "info" line in the logs is the thing that you were trying to do:

Doing thing 1...
Doing thing 2...
Doing thing 3...
Error!

If log messages are "post event", the error message needs to encompass what was being done.

Thing 1 done
Thing 2 done
Error doing thing 3!

I have a mild preference for the former, mostly because in the latter form, the error on thing 3 comes as a surprise - I didn't even know thing 3 was being done, and now it's failed!

We also need to differentiate between "interstitial" logs about specific tasks, and the "significant task with a prefix that define a context" - these definitely need to be "pre" not "post".

  • For Wait Bars, a few scenarios:

    • Wrapping a command with no output (even in its failure mode)

      • Use a Wait Bar without concern

+1; and the message should encompass the thing being done (i.e., `message="Doing thing...") so that the in-progress output is:

XXXXX-----XXXXX Doing thing...

And the final output is:

Doing thing... done.
  • Wrapping a command that always has output

    • Always print a regular log first stating what the command will do ending with an ellipsis

    • Then start the command in a Wait Bar

      • It may make sense to leave message="" so it isn't just reiterating the same thing.
      • In the case message="", I think making the Wait Bar 40 characters again would be nice.
      • The Wait Bar implementation would also be updated to print the done_message at the end even if message=""

I'd tweak this slightly:

  • A pre-logging "about to X.." message
  • a during/post logging message that the thing will be done (i.e., "X")

I'd also keep the smaller size of the wait bar; this accommodates for longer "X" descriptions, and also means there is only one "throb" pulsing at any one time.

If you had a command that produces 2 lines of output, this would yield the following as in-progress output (after 1 line had been generated):

Doing thing...
frobbing the widget
XXXXX-----XXXXX thing

And final output of:

Doing thing...
frobbing the widget
twiddled the gadget
thing done.
  • Wrapping a command that could have output

    • Depending on the circumstance, I think it's rational to fallback to either one of the scenarios above.
    • For instance, if the command fails and an Exception is raised, just using a Wait normally Bar is probably fine.
    • Alternatively, if the command fails, some stuff about what happened is printed, and execution continues....I think it'd be better to use the other scenario so when the user scrolls back up to it for review, it would all likely be clearer for them.

Agreed that "it depends" is the best guidance; if possible, it would be desirable to avoid:

Doing thing...
frobbing the widget
thing done.
Error twiddling the gadget!

or worse:

Doing thing...
frobbing the widget
Error twiddling the gadget!
thing done.

i.e., Ideally the "thing done" line wouldn't be output at all in the case of an error.

Also - "error" in this sense is only "unrecoverable error"; there's a couple of situations where an error is expected, and can be handled in a way that allows processing to continue.

Now, as for a broad logging policy:

  • Start significant tasks with a "prefix log"

    • This establishes a "context" to complete a task/outcome and communicate about subsequent "subtasks".
    • In this way, practically all logging is either a "context" or a "subtask".
    • Any subtask should be logged within a context.
    • (Taking this further, contexts are really just subtasks for the overall command.)

+1

  • Logging subtasks within a context

    • Generally, any "meaningful" step should be logged as a subtask.

      • Soo, as for what's meaningful....I guess like goldilocks, find the middle ground of what makes sense to log.
      • If a command could potentially fail the entire context, it should almost certainly be logged as subtask for the user.

+1

  • Use a Progress Bar for an enumerable subtask that's likely to take appreciable time

    • E.g. downloading a file or applying the same function against every item in an iterable.
    • A regular log call (or at least intervening log calls for each iteration of progress) should always proceed a Progress Bar to explain it.
  • Use a Wait Bar for a subtask with indeterminate but likely appreciable length.

    • Follow rules above for how to use the Wait Bar.
  • Otherwise, report the success or failure of a subtask after it runs with a regular log.

+1 except for the discussion points I've raised above.

Notes:

  • I've tried to boil down the answer to "when should a Wait Bar be used?" to basically be "use a Wait Bar when the user is likely to be waiting for a command to finish". Otherwise, just run the command and then tell the user what happened.
  • So, I'm definitely with you at this point on my backing off of using the Wait Bar so much.

+1; although, it's worth noting that in some cases, the fix here may be wrapping N micro-waitbars into a single waitbar. For example, Linuxdeploy post processing involves a bunch of small operations that almost never fail, produce no output except in case of a very rare error, and take almost no time. Wrapping all those operations into a single waitbar with a "post-processing binary..." message seems totally appropriate. In that type of situation, a waitbar effectively becomes a good way to decompose individual, largely reliable single operations into a "subtask" inside a context.

Interestingly, if we take this approach of grouping reliable minor tasks into waitbars, the question about what to do with standalone info() lines almost becomes moot, because there's almost no use case for a standalone info().

Please let me know what I didn't explain very well. If we like this plan, I still need to implement it in this PR....I can probably do that tomorrow.

Definitely clear. Also - please consider the opinions I've voiced here "strong opinions, weakly held"; if you think what I've described is either technically infeasible, or will result in a bad user experience, I'm more than happy to reconsider.

@rmartin16

Copy link
Copy Markdown
Member Author

If log messages are "post event", the error message needs to encompass what was being done.

Yeah...this is a bit my concern as well since by the time we're logging something, we're already in an error state, so we need to explain what we were doing and how/why it failed. However, given how much logging will be encompassed in "prefix logs" and Wait Bars, I expect this really just leaves pretty straight-forward tasks....and I expect nearly all of them end up as caught exceptions when they fail. So, my expectation is these tasks can pretty simply be refactored from:

print("about to do thing...")
try:
    perform task
except:
    raise Fail

to:

try:
    perform task
    print("Did thing successfully")
except:
    print("Failed attempting thing")
    raise Fail

In this way, the original message saying something is about to happens becomes a success message....or a failure message and then the explanation for why it failed.

Nonetheless, I don't really want to force this.....so, I'll play around with it and see what makes sense....and just how many "subtasks" would be affected by this strategy.

We also need to differentiate between "interstitial" logs about specific tasks, and the "significant task with a prefix that define a context" - these definitely need to be "pre" not "post".

Absolutely. This also helps ensures there is already context to what's going on when/if a subtask fails.

i.e., Ideally the "thing done" line wouldn't be output at all in the case of an error.

If the error manifests as an Exception, then the Wait Bar itself, at least, will not print a "done" message. But agreed...we should limit the "done" messages to subtasks that complete successfully.

The fix here may be wrapping N micro-waitbars into a single waitbar.[...] In that type of situation, a waitbar effectively becomes a good way to decompose individual, largely reliable single operations into a "subtask" inside a context. [...] The question about what to do with standalone info() lines almost becomes moot, because there's almost no use case for a standalone info().

Agreed. That linuxdeploy example is a good one since it could have output but is very unlikely to since its unlikely to fail....so just wrapping it all in a simple Wait Bar definitely makes sense.


I think we're largely on the same page, though....unless you have particular reservations about my post-event logging for the more trivial subtasks. I will pursue a commit tomorrow so our discussion can get a bit more concrete.

@freakboy3742

Copy link
Copy Markdown
Member

I think we're largely on the same page, though....unless you have particular reservations about my post-event logging for the more trivial subtasks. I will pursue a commit tomorrow so our discussion can get a bit more concrete.

Agreed that we seem to be on the same page; Any reservations I have about post-event logging will be a lot more productive to discuss as specific examples rather than in the abstract.

- Support subprocess.run calls within a Wait Bar
- Implement liberal use of Wait Bar
- Apply consistency of messages to users irrepective of workflow
@rmartin16 rmartin16 force-pushed the run-in-wait-bar branch 4 times, most recently from fb8a1fd to b1d3aee Compare June 10, 2022 17:32
# Check for a device skin. If it doesn't exist, download it.
skin_path = self.root_path / "skins" / skin
if skin_path.exists():
self.command.logger.info(f"Device skin '{skin}' already exists")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message never seemed particularly valuable to me...

@rmartin16

Copy link
Copy Markdown
Member Author

So, after reviewing all the calls to info() and wait_bar() with my previous post with mind, I didn't really find a lot that needed to be changed to comply.

Perspective
My ultimate goal is to make it easier for the logging to inherently be consistent and coherent. By moving the empty info() call in to Log, that eliminates nearly all of the need to make empty info() calls (outside of prompting the user). Therefore, future devs can simply do logging without the overhead of considering how it shows on the screen. By and large now, as long as you're using info() with and without prefix appropriately and ensuring that uses of a Wait Bar provide understandable chronology after the fact, the UI should keep itself consistent without devs needed to worry too much.

Pre/Post-Event Logging
As far as pre/post-event logging, I didn't really make any changes for this. One, there simply isn't a lot of it going on anymore with the use of Wait Bars. Two, the situation mostly dictates which is appropriate. For instance, post-event logging works quite well for patching the linuxdeploy ELF headers. However, pre-event logging makes much more sense for Apple app signing (in sign_file()) given it makes use of falling back and would require "backtrack logging" as it were to keep track.

Wait Bar Use Semantics
As for Wait Bar logging chronology....I maintain that this is something that deserves cognitive overhead with the current implementation.
For the most part, Wait Bars are wrapping things that rarely/never fail or fail & abort the entire briefcase command. Just wrapping these commands with a Wait Bar should be fine since while any command output will technically come before the Wait Bar's message on the screen, the fact that one thing was taking place and it failed should help prevent confusion about the ordering on the screen.
There are cases, though, where several Wait Bars are chained one after the other and each command could have its own output. This is where I think it will be important to implement initial info() calls to say what's happening and then enter a Wait Bar. This is most pronounced from my changes for the build command. Each target platform (except Android) now uses this approach. It's slightly repetitive.....but I think this approach provides more clarity...especially in situations where things go wrong.

Finally, all of my intended code changes are in....however, I have to run today and cannot run through more thorough manual testing; I'll do that this weekend. You're welcome to comment on the code, though, before then.

Comment thread src/briefcase/integrations/linuxdeploy.py
Comment thread tests/integrations/android_sdk/AndroidSDK/test_start_emulator.py
 - Miscellaneous fixes
 - Sign app in Progress Bar instead of Wait Bar
 - Standardize logging and Wait Bar usage
@codecov

codecov Bot commented Jun 12, 2022

Copy link
Copy Markdown

Codecov Report

Merging #755 (bde0524) into main (d7e9aa7) will decrease coverage by 0.01%.
The diff coverage is 100.00%.

Impacted Files Coverage Δ
src/briefcase/commands/build.py 100.00% <ø> (ø)
src/briefcase/commands/dev.py 95.00% <ø> (-0.17%) ⬇️
src/briefcase/commands/package.py 85.00% <ø> (-0.37%) ⬇️
src/briefcase/commands/update.py 100.00% <ø> (ø)
src/briefcase/commands/upgrade.py 94.91% <ø> (-0.25%) ⬇️
src/briefcase/integrations/docker.py 92.30% <ø> (-0.20%) ⬇️
src/briefcase/integrations/xcode.py 94.11% <ø> (ø)
src/briefcase/platforms/macOS/app.py 100.00% <ø> (ø)
src/briefcase/commands/create.py 99.62% <100.00%> (-0.01%) ⬇️
src/briefcase/console.py 100.00% <100.00%> (ø)
... and 11 more

Comment thread src/briefcase/console.py
# Signal that Rich is dynamically controlling the terminal output.
# Therefore, all output must be printed to the screen by Rich to
# prevent corruption of dynamic elements like Wait Bars.
self.is_output_controlled = False

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to generalize the indication and handling of an active Wait Bar in to any active Rich content. However, "there are two hard things in computer science: cache invalidation and naming things". I struggled with a name for this flag that communicated what its actually indicating. Open to a better name.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the full quote is "2 hard problems; cache invalidation, naming things, and off-by-one errors"... 😛

I can't say an obviously better name is jumping out at me, so I'm happy to go with this.

@rmartin16

rmartin16 commented Jun 12, 2022

Copy link
Copy Markdown
Member Author

This is ready for final review now.

As a P.S.....one idea I kept coming back to to handle output chronology in a Wait Bar is changing the output semantics of the Wait Bar. Currently the Wait Bar will pin itself to the bottom of the terminal until the Wait Bar exits. Anything that's printed while it is active will inherently appear above the "message" and its "done" indication.

Alternatively, the Wait Bar could print the "message" statically and then pin the pulsing bar to the bottom of the terminal. When the Wait Bar exits, a "done" message would be printed underneath the "message". In this way, any intervening output would be sandwiched between the "message" and "done". This is basically what I did manually for the build command code flows....although, I haven't been able to convince myself it's what's best for all Wait Bars. (A more extravagant version of this idea would convert a Wait Bar from its current implementation in to one that can sandwich any printed content....although, this would probably be overly complex to create....and probably unnecessary given our use-case.)

@freakboy3742 freakboy3742 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is awesome. No notes. 💯

Regarding your final notes about other options for waitbar - I can see what you're saying about alternatives to how waitbar operates. My concern about having a top-pinned message is that you'll lose the context about what the "Done" is actually referring to. I actually found the way you've got it set up here to be helpful, because a long-running noisy compile (e.g., Linux AppImage builds) gives you the visual context of what is currently spewing output. I'm sure we could dream up a different visualisation that top pins the progress bar, but I'm also sure that would end up being a nightmare to implement and maintain, so I'm happy to go with what you've got here.

Once again, thanks for an awesome contribution. (It's just so pretty! 😍)

Comment thread src/briefcase/console.py
# Signal that Rich is dynamically controlling the terminal output.
# Therefore, all output must be printed to the screen by Rich to
# prevent corruption of dynamic elements like Wait Bars.
self.is_output_controlled = False

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the full quote is "2 hard problems; cache invalidation, naming things, and off-by-one errors"... 😛

I can't say an obviously better name is jumping out at me, so I'm happy to go with this.

@freakboy3742 freakboy3742 merged commit 887dec4 into beeware:main Jun 16, 2022
@rmartin16 rmartin16 deleted the run-in-wait-bar branch July 1, 2022 16:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants