-
Notifications
You must be signed in to change notification settings - Fork 527
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
[Feature Request]: Introduce new version code strategy #5033
Comments
Curious about your thoughts on the approach @seanlip and @adhiamboperes. This is semi-permanent so we need to be careful about how we proceed when it comes to "wasting" version codes, but I think that:
If you're both fine with this approach, this is something that could probably be implemented while I'm out (though I ask that it's kept from merging until I get back to review it, if that's okay). |
Thanks @BenHenning. I thought about this a bit and I want to make sure we're on the same page about the problem before discussing the solution. What is the actual problem statement? I'm going to have a go at enumerating it in terms that I understand, but it's almost certainly got some wrong bits so please feel free to correct it. (You're welcome to edit this comment if you like.) Please also feel free to restate the problem in your own terms if what follows is largely off-base. Background info
Use cases
Constraints and AssumptionsThis is the part I am not sure about.
Proposed Solution
|
Thanks for summarizing @seanlip. You have most of the details correct, but there are some adjustments needed. I also want to clarify on the points indicated by your questions. Background info (adjusted)
Use cases
Re: Constraints and Assumptions
Notice that in the above a perfect release (i.e. one with no CPs required) would actually use the same base commit for the release all the way to GA which means every version code (minus dev) would actually end up being used. The alternative would be to somehow tie the release branch type to the flavors using its version codes, and that gets really complicated & messy. We really only "lose" version codes in this system due from:
The system proposed is planning against the worst case, but in reality we'd likely have such a large pool of version codes that even with liberally wasting them we should still never run out (see ~40 year calculation). It's also assuming 1 hourly release per PR at 100 PRs per day (which is mathematically not possible, plus CI runs will delay the hourly rate). In reality, it's probably more like ~200 years of version codes even at 100x productivity. ** This is a special case. The regression testing does NOT occur with builds distributed through the Play Store. The reason for this is that the needs of testing for regressions vs. features are different, and we can't have more than one internal testing track. Instead, testers will manually install builds from a shared location (probably an automatically maintained Google Drive folder) that are undergoing regression testing. The assumption also here is that this is largely minimized over time by automated end-to-end testing.
Yep, exactly for the reason you mentioned we want to maintain this invariant.
Yes to the first, and not quite to the second. See my reply two prompts up to see the planned process for how releases will be promoted from develop to GA. Re: Proposed Solution
Because of the worst case being that 1 commit to develop (i.e. one merged PR) can correspond to a single GA release, we need to make sure that each commit has enough version codes to sustain both the build flavors we manage today (plus others we may add), as well as the number of potential RCs we may create (assuming 1 RC per direct commit to a release branch, e.g. for a cherry-pick). The original comment goes into the specifics on calculating this, but I'm proposing we allocate 1000 version codes per prospective release (i.e. per single commit to develop) which, divided the proposed above, allows for up to 40 release candidates and 25 build flavors per release candidate (both of which should be more than we ever need). At that amount, we can create 2 million release branches before running out of version codes. Per your second question, version codes "exist" for all the branches, but we only compute them relative to the develop & release branches. This is an important thing to note because version codes greatly affect the ease with which local development can take place (switching between branches is painful if the version code downgrades as it requires uninstalling the app locally to install a new build).
Per the last bit, I'd also rather use commit hashes (which is what we use for the app version names) in conjunction with build flavors, but unfortunately we have somehow turn that into a monotonically increasing integer. The quick way to do the math for version code allotment under the proposal is to multiply the number of commits to develop by 1000 (with an initial shift at the time of introducing the system so we don't waste ~1.8 million version codes outright), and then add the number of active build flavors for each commit to the release branch. Following are examples for better illustrating the two cases you asked about. Both cases are assuming the following build flavors: dev, testing, alpha, beta, ga. Case 1: cut release branch, push to develop, commit to release branch.
Case 2: cut release branch, commit to release branch, push to develop.
It can be seen that the proposed solution is order independent since it's absolutely computing version codes based on a change's position relative to the first commit to the develop branch (where changes on develop increment by 1000 version codes, and changes on release branches increment by the number of build flavors). Does this better help illustrate how this system would work? Edit: it should be notes that the proposed solution is using the current number of build flavors available at a given release commit. Since we're using up the version codes in the pool anyway, we could also change the formula to always account for the maximum number of version codes. For example, this changes scenario 1 above to: Case 1 (revised): cut release branch, push to develop, commit to release branch.
This has the advantage of making it possible to calculate the exact release number and candidate within that release for any given version code. I'm not sure that really matters, but it's a potential nice side effect of this other approach. |
Is your feature request related to a problem? Please describe.
Version codes currently need to be manually updated and then cherry-picked for each release candidate going out (#5029 as an example).
Describe the solution you'd like
While there is a plan to move toward automated releases, the amount of develop & release branch churn needed just to bump version codes seems far too high.
I'm proposing that we move to a calculated approach. If we assume the following:
Then we can calculate that Y version codes are needed per release candidate, and thus X * Y are needed per release branch/commit to develop. This provides us a way to absolutely calculate the release version code in a completely stateless way (or, rather, only depending on the state of Git itself).
The two main challenges here are:
Tuning X & Y
I think it's a good idea to plan backwards from the maximum, actually. Per https://developer.android.com/studio/publish/versioning#versioningsettings the largest version code that can be accepted is 2100000000 (though https://stackoverflow.com/a/38847981/3689782 indicates this might actually be 2000000000). Technically SDK 28 supports long integer versions (https://stackoverflow.com/a/38847981/3689782) but it's unclear how this interoperates with Play Store. I think we have to assume that's not going to be an option for a long time both because of compatibility and because it's unlikely for us have a min target SDK of 28 for many years.
Based on that, I'm aiming for using no more than 1000 version codes per prospective release as this will net us a capacity of submitting up to 2 million commits to our develop branch before we run out of release "slots." Our current development behavior is comprised entirely of manually reviewed PRs, and I suspect that won't change anytime soon (since the only prospective auto-submitted PRs would be generating version codes, and this proposal avoids that). The team's velocity is approximately 1.3 PRs submitted per day. If we 100x'd that (i.e. 130 PRs merged per day), this upper limit of version codes would require 45 years of sustained 100x velocity before we risk running out of version codes.
With 1000 version codes per release we can tune X and Y as follows:
We need to account for a larger X because each unique cherry-pick could potentially be a new commit (where we aren't squashing/combining them, plus future automation may lead to a 1:1 generation of cherry-picked PRs to release branch commits), as well as potential manual branch changes (e.g. if a failed cherry-pick requires fixing). Since the team is planning on having a fairly fast release cycle, 40 commits to the release branch seems well above anything we'd need.
We currently have 7 build flavors, 2 of which are KitKat-specific and will be removed (per #5012), and 1 of which is unique to a user study and will be removed (per #4419). As we progress, there are essentially two factors which determine the number of build flavors needed:
I think we can assume (1) will always be dev + internal testing + alpha + beta + GA + extra alpha/Firebase tracks (so a minimum of 5 build flavors required).
(2) is being simplified with the removal of KitKat, but we can't be sure future releases of Android won't lead to us needing to segment the release in a similar way. This needs to be done carefully since each configuration doubles the number of flavors needed. At 5 base build flavors + 1 extra alpha track, this means we can only support 2 build configurations (putting us at 24 total flavors).
Fortunately, Android App Bundles (AABs) help simplify (2) by providing ways of condensing build configurations into single binaries. While KitKat was a special case, other scenarios are quite possibly going to lead to need different variations of a build flavor based on the device: screen densities, ABIs (if we ever ship native code), and even languages. AABs can support all of these to avoid needing extra flavors (see https://github.com/google/bundletool/blob/f17ce94a4c10555e7ba87bbf4d4f2af35b70cc61/src/main/proto/config.proto#L258 for currently supported split configs). Given this, the likelihood of needing such a configuration is quite low over the long-term, and we're effectively budgeting for 2 simultaneous possibilities.
Note that we can always re-tune X & Y if we don't hit their respective maximums, or by using a different formula after a particular version code.
Residual benefits
This approach guarantees that any single valid version code corresponds to exactly a single unique AAB configuration with unique changes. That can actually simplify our infrastructure as version codes can become a proper check for "is this binary different?" whereas it can't today since multiple commit branches share the same version code.
Describe alternatives you've considered
I considered all of the following (& why I dismissed each):
I'm sure there are many other possibilities--feel free to follow up on this issue thread if you have any that might work better than what's proposed above.
Additional context
Implementation thoughts
Version codes are essentially only needed when configuring the app's APK and AAB binaries, but they're needed as integers at compile time within Bazel. This poses an issue because computing the version codes requires access to the local Git repository and the Git tool. I think rather than trying to get this to work properly in Bazel, we can short-circuit it by removing the version code management in Bazel and just compute the corresponding version within TransformAndroidManifest directly.
As for computing the version codes, there are two parts:
Both require computing the number of commits that a branch has, and need to be done carefully so that the calculation works both on develop and release branches.
The number of commits to develop can be computed using:
The number of commits to a particular release branch can be computed using (see https://stackoverflow.com/a/11657647/3689782):
The above correctly evaluates to 0 when on the develop branch (or any of its earlier branches).
Thus, the formula (expressed in Bash), assuming we don't exceed 1000 version codes with the current solution, would be:
BASE_VERSION_CODE=$((100+(($(git rev-list --count develop)-1835)*1000)+($(git rev-list --count HEAD ^develop)*25)))
Note this intends to offset the calculation by the number of develop commits at the time the solution is implemented (to avoid wasting ~2 million version codes).
Implementation caveats
One important caveat to note on the implementation approach is that it will result in non-release/non-develop branches effectively becoming release candidate-like in their version codes. This will be disruptive to development as switching between commits or even branches on a PR chain should not result in version downgrades during installation.
To mitigate for this, we should verify that the branch the user is on is either develop or a release branch (e.g. starts with "release-") when computing the version codes. If they are not on either, assume they are on their develop branch merge-base (the common commit, i.e. the latest commit of develop from which the branch is based). This will maintain parity with the way version codes are handled today (minus that updating to develop will result in new version codes, but that's already a possibility today).
The text was updated successfully, but these errors were encountered: