-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
[Bug][Proposal]: New Termux app versionCode spec to solve app store issues #4012
Comments
@Grimler91 @sylirre @fornwall Let me know what you think. |
This comment was marked as off-topic.
This comment was marked as off-topic.
Looks good and it would be enough to have just two version priorities (PS and non-PS releases, non-PS preferred). |
Yeah, true, less work and divergence too. If we ever need a priority above default |
A similar proposal was made to semver at semver/semver#516 without the |
Neat scheme, looks to me like it would work. Being able to tell if a version is beta or not from versioncode is nice but I guess not necessary |
Looks like it could work! As a version scheme like this is not something to rush into (given the mess of making any changes to it later on), it might be good to give it some time&thought and room for feedback. So perhaps a |
The The full
The
The fields are:
The regex
The spec effectively allows Apps can use the
The only other issue with the Termux app GitHub action builds use the Following are examples of
Following are examples of versions with different
Following is the #!/usr/bin/bash
# shellcheck shell=bash
AASSU__LOG_LEVEL=1
AASSU__DEFAULT_NOOP=0
AASSU__DEFAULT_RELEASE_VARIANT=4
AASSU__MAX_VERSION_CODE=1073741808
show_help() {
cat <<'HELP_EOF'
android-app-semver-spec-utils.sh provides utils to convert versions
that are compliant with the `android-app-semver` spec.
Available commands:
name-to-code Convert a version name to a version code.
code-to-name Convert a version code to a version name.
Usage:
android-app-semver-spec-utils.sh [command_options] name-to-code \
<version_name> [[release_variant] [noop]]
android-app-semver-spec-utils.sh [command_options] code-to-name \
<version_code>
Available command_options:
[ -h | --help ] Display this help screen.
[ -v ] Set log level to `DEBUG`.
For the `name-to-code` command:
- The `release_variant` and `noop` arguments are optional. Either
both should be passed, or none, or only `release_variant`.
The default value is `4` for `release_variant` and `0` for `noop`.
- The `version_name` must be in the format
`major.minor.patch(-(alpha|beta|rc).<revision>)`.
- The `major` value must be between `0-63` (inclusive).
- The `minor` value must be between `0-511` (inclusive).
- The `patch` value must be between `0-31` (inclusive).
- The `(-(alpha|beta|rc).<revision>)` part is optional and if not
passed, it is conidered a `stable` `release_type`. If passed, then
`revision` must be between `1-15` (inclusive), like `-beta.1`.
- The `release_variant` value must be between `0-7` (inclusive).
- The `noop` value must be between `0-1` (inclusive).
Examples:
android-app-semver-spec-utils.sh name-to-code 0.1.0
android-app-semver-spec-utils.sh name-to-code 0.1.0-beta.1 0
android-app-semver-spec-utils.sh name-to-code 63.511.31 7 1
For the `code-to-name` command:
- The `version_code` value must be between `0-1073741808` (inclusive)
and must be a valid value as per spec.
Examples:
android-app-semver-spec-utils.sh code-to-name 268437552
Pass the `-v` flag to print additonal info for version parts, which
is useful to debug issues.
HELP_EOF
}
main() {
local return_value
if [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
show_help || return $?
return 0
else
if [ "${1:-}" = "-v" ]; then
AASSU__LOG_LEVEL=2
shift
fi
if [ "$1" = "name-to-code" ]; then
shift
name_to_code "$@"
return_value=$?
elif [ "$1" = "code-to-name" ]; then
shift
code_to_name "$@"
return_value=$?
else
echo "Invalid command '$1'." 1>&2
return_value=64
fi
if [ $return_value -eq 64 ]; then # EX__USAGE
echo ""
show_help
fi
return $return_value
fi
}
name_to_code() {
if [ $# -lt 1 ] || [ $# -gt 3 ]; then
echo "Invalid argument count $#. The \"name_to_code\" method expects 1 to 3 arguments." 1>&2
printf 'Arguments: %s\n' "$*" 1>&2
return 64
fi
local version_name="${1/v/}"
local release_variant="${2:-$AASSU__DEFAULT_RELEASE_VARIANT}"
local noop="${3:-$AASSU__DEFAULT_NOOP}"
local version_name_regex='^([0-9]|[1-5][0-9]|6[0-3])\.([0-9]|[1-9][0-9]|[1-4][0-9][0-9]|50[0-9]|51[0-1])\.([0-9]|[1-2][0-9]|3[0-1])(-(alpha|beta|rc)\.([1-9]|1[0-5]))?$'
if ! printf "%s" "$version_name" | grep -qE "$version_name_regex"; then
echo "The version_name '$version_name' is not valid as per android-app-semver spec compliant with semantic version '2.0.0' and in the format 'major.minor.patch(-(alpha|beta|rc).<revision>)'. https://semver.org/spec/v2.0.0.html." 1>&2
return 64
fi
if [[ ! "$release_variant" =~ ^[0-7]$ ]]; then
echo "The release_variant '$release_variant' for version_name '$version_name' is not valid and must be between 0-7 (inclusive)." 1>&2
return 64
fi
if [[ ! "$noop" =~ ^[0-1]$ ]]; then
echo "The noop '$noop' for version_name '$version_name' is not valid and must be between 0-1 (inclusive)." 1>&2
return 64
fi
local major
local minor
local patch
local release_type
local release_type_name
local revision
local version_code
local version_code_binary
major="$(printf "%s" "$version_name" | sed -E "s/$version_name_regex/\1/")" || return $?
minor="$(printf "%s" "$version_name" | sed -E "s/$version_name_regex/\2/")" || return $?
patch="$(printf "%s" "$version_name" | sed -E "s/$version_name_regex/\3/")" || return $?
release_type_name="$(printf "%s" "$version_name" | sed -E "s/$version_name_regex/\5/")" || return $?
revision="$(printf "%s" "$version_name" | sed -E "s/$version_name_regex/\6/")" || return $?
case "$release_type_name" in
alpha) release_type=0;;
beta) release_type=1;;
rc) release_type=2;;
*) release_type_name="stable"; release_type=3; revision=0;;
esac
if [[ "$AASSU__LOG_LEVEL" == "2" ]]; then
print_verbose_version_info || return $?
fi
if [ $release_type -lt 3 ] && [ $revision -lt 1 ]; then
echo "The revision '$revision' for version_name '$version_name' is not valid and must not be 0 for a non-stable release with type '$release_type_name'." 1>&2
return 1
fi
version_code=0
version_code=$(( version_code + revision )) || return $? # 0 000 000000 000000000 00000 00 1111
version_code=$(( version_code + (release_type << 4) )) || return $? # 0 000 000000 000000000 00000 11 0000
version_code=$(( version_code + (patch << 6) )) || return $? # 0 000 000000 000000000 11111 00 0000
version_code=$(( version_code + (minor << 11) )) || return $? # 0 000 000000 111111111 00000 00 0000
version_code=$(( version_code + (major << 20) )) || return $? # 0 000 111111 000000000 00000 00 0000
version_code=$(( version_code + (release_variant << 26) )) || return $? # 0 111 000000 000000000 00000 00 0000
version_code=$(( version_code + (noop << 29) )) || return $? # 1 000 000000 000000000 00000 00 0000
if [ $version_code -lt 0 ] || [ $version_code -gt $AASSU__MAX_VERSION_CODE ]; then
echo "The version_code '$version_code' for version_name '$version_name' is not valid and must be between 0-$AASSU__MAX_VERSION_CODE (inclusive)." 1>&2
return 1
fi
version_code_binary="$(decimal_to_binary "$version_code" 30)" || return $?
version_code_binary="$(printf "%s\n" "$version_code_binary" | sed -E 's/(.{1})(.{3})(.{6})(.{9})(.{5})(.{2})(.{4})/\1 \2 \3 \4 \5 \6 \7/')" || return $?
print_formatted_version_info || return $?
}
code_to_name() {
if [ $# -ne 1 ]; then
echo "Invalid argument count $#. The \"name_to_code\" method expects 1 argument." 1>&2
printf 'Arguments: %s\n' "$*" 1>&2
return 64
fi
local version_code="$1"
if [[ ! "$version_code" =~ ^[0-9]+$ ]] || [ "$version_code" -gt $AASSU__MAX_VERSION_CODE ]; then
echo "The version_code '$version_code' is not valid and must be between 0-$AASSU__MAX_VERSION_CODE (inclusive)." 1>&2
return 64
fi
version_code_binary="$(decimal_to_binary "$version_code" 30)" || return $?
version_code_binary="$(printf "%s\n" "$version_code_binary" | sed -E 's/(.{1})(.{3})(.{6})(.{9})(.{5})(.{2})(.{4})/\1 \2 \3 \4 \5 \6 \7/')" || return $?
if [[ "$AASSU__LOG_LEVEL" == "2" ]]; then
echo "version_code_binary: $version_code_binary"
fi
local major
local minor
local patch
local release_type
local release_type_name
local revision
local version_name
noop=$(( (version_code & 536870912) >> 29 )) || return $? # 1 000 000000 000000000 00000 00 0000
release_variant=$(( (version_code & 469762048) >> 26 )) || return $? # 0 111 000000 000000000 00000 00 0000
major=$(( (version_code & 66060288) >> 20 )) || return $? # 0 000 111111 000000000 00000 00 0000
minor=$(( (version_code & 1046528) >> 11 )) || return $? # 0 000 000000 111111111 00000 00 0000
patch=$(( (version_code & 1984) >> 6 )) || return $? # 0 000 000000 000000000 11111 00 0000
release_type=$(( (version_code & 48) >> 4 )) || return $? # 0 000 000000 000000000 00000 11 0000
revision=$(( (version_code & 15) )) || return $? # 0 000 000000 000000000 00000 00 1111
version_name="$major.$minor.$patch"
case "$release_type" in
0) release_type_name=alpha;;
1) release_type_name=beta;;
2) release_type_name=rc;;
*) release_type_name=""
esac
if [[ "$AASSU__LOG_LEVEL" == "2" ]]; then
print_verbose_version_info || return $?
fi
if [ -n "$release_type_name" ]; then
if [ $revision -lt 1 ]; then
echo "The revision '$revision' for version_code '$version_code' is not valid and must not be 0 for a non-stable release with type '$release_type_name'." 1>&2
return 1
fi
version_name+="-$release_type_name.$revision"
else
if [ "$revision" -ne 0 ]; then
echo "The revision '$revision' for version_code '$version_code' is not valid and must be 0 for a stable release." 1>&2
return 64
fi
fi
print_formatted_version_info || return $?
}
print_verbose_version_info() {
echo "noop: $noop"
echo "release_variant: $release_variant"
echo "major: $major"
echo "minor: $minor"
echo "patch: $patch"
echo "release_type: $release_type (${release_type_name:-stable})"
echo "revision: $revision"
echo
}
print_formatted_version_info() {
printf "version_name: %17s, version_code: %10s (%s), release_variant: %1s" "$version_name" "$version_code" "$version_code_binary" "$release_variant" || return $?
if [ "$noop" -gt 0 ]; then
printf ", noop: %1s" "$noop" || return $?
fi
echo
}
decimal_to_binary() {
local decimal="$1"
local min_digits="$2"
if [[ ! "$min_digits" =~ ^[0-9]+$ ]]; then
min_digits=8
fi
local binary
local binary_digits
local zeros=""
local zeros_required
binary="$(printf "%s\n" "ibase=10;obase=2;$decimal" | bc)" || return $?
binary_digits=${#binary}
if [ "$binary_digits" -lt $min_digits ]; then
zeros_required=$((min_digits - binary_digits))
zeros=""
for (( i = 0; i < "$zeros_required"; i++ )); do
zeros="${zeros}0"
done
fi
printf "%s\n" "$zeros$binary"
}
# If running in bash, run script logic, otherwise exit with usage error
if [ -n "${BASH_VERSION:-}" ]; then
# If script is sourced, return with success, otherwise call main function
# - https://stackoverflow.com/a/28776166/14686958
# - https://stackoverflow.com/a/29835459/14686958
if (return 0 2>/dev/null); then
return 0 # EX__SUCCESS
else
main "$@"
exit $?
fi
else
(echo "${0##*/} must be run with the bash shell."; exit 64) # EX__USAGE
fi |
I prefer the Another issue was that any pre-releases like I also have decided not to use app version names in packages to run logic, it should instead be based on environment variables, as Termux app and plugin forks and apps using termux-core library will have different versions than upstream Termux app, and they may merge multiple plugin apps into a single app, or only enable/integrate specific features depending on what they want or is possible as per different app store policies and exemptions they get. Instead different Termux APIs like |
The `versionCode` has been bumped to `1000` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list due to non-collaborative `v0.120` app release on PlayStore that set the `versionCode` higher than the latest F-Droid or GitHub `118` release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch. - #4000 - #4012
The `versionCode` has been bumped to `1020` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list due to non-collaborative `v0.120` app release on PlayStore that set the `versionCode` higher than the latest F-Droid or GitHub `118` release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch. The `v0.118.1` was released under `versionCode` `1000`, we bump `versionCode` to `1020` so that there are `20` version codes in between that can be used as patch releases for `0.118.x` in case needed, like for `v0.118.2`. - #4000 - #4012
The `versionCode` has been bumped to `1000` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list due to non-collaborative `v0.120` app release on PlayStore that set the `versionCode` higher than the latest F-Droid or GitHub `8` release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch. - termux/termux-app#4000 - termux/termux-app#4012 - termux-play-store/termux-apps@9c10607
The `versionCode` has been bumped to `1000` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list due to non-collaborative `v0.33` app release on PlayStore that set the `versionCode` higher than the latest F-Droid or GitHub `32` release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch. - termux/termux-app#4000 - termux/termux-app#4012 - termux-play-store/termux-apps@b47c83d
The `versionCode` has been bumped to `1000` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list due to non-collaborative `v0.14` app release on PlayStore that set the `versionCode` higher than the latest F-Droid or GitHub `13` release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch. - termux/termux-app#4000 - termux/termux-app#4012 - termux-play-store/termux-apps@e51f8d2#diff-afedc8b49d567d2de57705052be9f6521366702c15f6ea32b2ae45174cef8221
The GitHub Actions build still uses the 118 versionCode, Google Play tries to update it. Would it be possible to set a specific versionCode just for the automated workflows? Maybe set it a very high number, as it is like a nightly build. |
The `versionCode` has been bumped to `1020` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list due to non-collaborative `v0.120` app release on PlayStore that set the `versionCode` higher than the latest F-Droid or GitHub `118` release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch. The `v0.118.1` was released under `versionCode` `1000`, we bump `versionCode` to `1020` so that there are `20` version codes in between that can be used as patch releases for `0.118.x` in case needed, like for `v0.118.2`. - termux#4000 - termux#4012
The `versionCode` has been bumped to `1000` so that users who have installed from F-Droid or GitHub should not have the app attempted to be updated by Google PlayStore and failing and also shown in PlayStore app updates list in case the `versionCode` is set to higher than the latest F-Droid or GitHub release. Unlike F-Droid, PlayStore does not check for difference in app APK signature before attempting to download and then failing to install due to signature mismatch. - termux/termux-app#4000 - termux/termux-app#4012
Android and Google PlayStore/F-Droid stores use the
versionCode
positive integer value of the currently installed app to decide whether the new APK to be installed or one hosted by the store is an updated version or not. If theversionCode
of the new APK is higher, only then it is considered an update and allowed to be normally installed over the currently installed app. TheversionName
string value of apps is not used in this decision.The max value for
versionCode
that Google PlayStore allows is2 100 000 000
(2100000000
), Android also internally stores it as anint
and javaINT_MAX
value is only slightly higher2 147 483 647
.Since termux-apps use semantic versioning
2.0
forversionName
in the formatx.y.x
since0.118.0
release, there needs to be some way for mapping theversionName
to aversionCode
while keeping it different for different install sources so that their app stores don't show updates of other sources. This is a problem for Google PlayStore because it does not check signatures of the currently installed app APK against the latest app APK hosted by the store that is to be updated, and it only compares theversionCode
. So if the device has an APK installed from a different install source with a different signature, but itsversionCode
is lower than the latest APK hosted in the store, then PlayStore will attempt to download and install it and then fail with aCan't install Termux
generic UI error, with the actual errorFinsky Submitter: commit error message INSTALL_FAILED_UPDATE_INCOMPATIBLE: Existing package com.termux signatures do not match newer version; ignoring!
logged tologcat
that is returned by the Android package installer. User can manually disable auto update for the app from its app page options button, but any updates will still show in the PlayStore app updates list. Basically, to solve this, theversionCode
of app APKs hosted by the "broken" stores must be lower than other install sources.Note that F-Droid store does not show updates for apps in the app updates list if they are signed with a different signing key. The APK signing certificate SHA-256 digest is stored in the
packages: <package_name>: versions: <version_id>: manifest: signer: sha256: 0
field of theindex
that F-Droid repository maintains for all hosted app APKs and it does not need to download the actual APK to find it. Thesigner
field is stored in themanifest_signer_sha256
field of theVersion
table of the/data/data/org.fdroid.fdroid/databases/fdroid_db
database that F-Droid app maintains. (1, 2) If the user manually tries to update an app from its app page, it is prevented with theThe new version is signed with a different key to the old one. To install the new version, the old one must be uninstalled first. Please do this and try again. (Note that uninstalling will erase any internal data stored by the app)
(1, 2) error, without actually trying to update the app and then failing.An incremental
versionCode
cannot be used, as that would not allow patch releases. For example,0.118.1
is released as1000
, then0.119.0-beta.1
is released as1001
. If0.118.1
F-Droid build fails or a bug is found after the release, then0.118.2
patch version update may need to be released, but if its now released as1002
, it will be considered as higher version than0.119.0-beta.1
(1001
), which it won't be. So each major/minor/patch update must have sufficient reserved slots after their next respective increment. Additionally, there must be reserved slots forbeta
releases before a stable release as well.The following versioning spec is proposed for mapping the
versionName
to aversionCode
while also keeping a priority for different install sources. As the Google PlayStore allows max value2 100 000 000
for aversionCode
, but since the first and second section cannot go above2 100
, the max value is limited to1 999 999 999
in the spec. Google PlayStore also suggests a similarversionCode
spec.noop
:0-1
(currently not used)release_variant
:1-9
(other values could be used for other stores like Samsung store)1
- PlayStore (lowest priority)5
- F-Droid or other sources like planned Termux site7
- GitHub and custom builds (highest priority)major
:0-99
minor
:0-999
patch
:0-99
beta/stable
:0-9
The effectively allows
1000
minor
releases for each of the100
major
releases. So a total of100 x 1000 = 1,00,000
(1
hundred thousand)major/minor
releases. If the100
patch
releases are also included, then total releases allowed would be100 x 1000 x 100 = 10,000,000
(10
million). Even though the semantic versioning spec does not limit the size ofmajor
/minor
/patch
, the1,00,000
releases should be sufficient for Termux's future. It will allow more than5
releases to be made per day for the next50
years (90000
). Different install sources will also never conflict and will also have1,00,000
releases each. Thenoop
could possibly be used fortargetSdkVersion
variants or some other config, or could be used to expand versions themselves.Following are examples of
versionCode
mapping fromversionName
with thenoop
andrelease_variant
parts not set (= 0
).Following includes the
release_variant
parts for different versions.At build time, the value
release_variant x 1 00 000 000
can be added to theversionCode
.1 00 000 000
5 00 000 000
7 00 000 000
The
release_variant
can be set by exporting the$TERMUX_APP_BUILD__VERSION_CODE_RELEASE_VARIANT
environment variable at build time, with the default value7
. For F-Droid, a pull request will need to be sent to export the value in theprebuild
step of thecom.termux
package metadata file.prebuild: - export TERMUX_APP_BUILD__VERSION_CODE_RELEASE_VARIANT=5
However, since F-Droid cannot dynamically detect generated
versionCode
, theversionCode
would need to be hardcoded inapp/build.gradle
build, like0 5 00 118 010
(versionCode 500118010
) for nextversionName
0.118.1
. It would be much simpler to just use priority5
for bothGitHub
andF-Droid
, and possibly other non-broken sources/stores, like Termux site. This would also not require sending a pull request and no special logic will be need to be added to handle F-Droid requirement by first subtracting5 00 000 000
fromversionCode
, and then adding$TERMUX_APP_BUILD__VERSION_CODE_RELEASE_VARIANT
.Related #4000
The text was updated successfully, but these errors were encountered: