diff --git a/fdroidserver/checkupdates.py b/fdroidserver/checkupdates.py index ecba9ba6..756a8064 100644 --- a/fdroidserver/checkupdates.py +++ b/fdroidserver/checkupdates.py @@ -379,7 +379,6 @@ def _getcvname(app): def fetch_autoname(app, tag): - if not app.RepoType or app.UpdateCheckMode in ('None', 'Static') \ or app.UpdateCheckName == "Ignore": return None @@ -417,14 +416,23 @@ def fetch_autoname(app, tag): return commitmsg -def checkupdates_app(app): +def operate_vercode(operation, vercode): + if not common.VERCODE_OPERATION_RE.match(operation): + raise MetaDataException(_('Invalid VercodeOperation: {field}') + .format(field=operation)) + oldvercode = vercode + op = operation.replace("%c", str(oldvercode)) + vercode = common.calculate_math_string(op) + logging.debug("Applied vercode operation: %d -> %d" % (oldvercode, vercode)) + return vercode + +def checkupdates_app(app): # If a change is made, commitmsg should be set to a description of it. # Only if this is set will changes be written back to the metadata. commitmsg = None tag = None - vercode = None mode = app.UpdateCheckMode if mode.startswith('Tags'): pattern = mode[5:] if len(mode) > 4 else None @@ -444,30 +452,33 @@ def checkupdates_app(app): else: raise MetaDataException(_('Invalid UpdateCheckMode: {mode}').format(mode=mode)) - if version and vercode and app.VercodeOperation: - if not common.VERCODE_OPERATION_RE.match(app.VercodeOperation): - raise MetaDataException(_('Invalid VercodeOperation: {field}') - .format(field=app.VercodeOperation)) - oldvercode = str(int(vercode)) - op = app.VercodeOperation.replace("%c", oldvercode) - vercode = str(common.calculate_math_string(op)) - logging.debug("Applied vercode operation: %s -> %s" % (oldvercode, vercode)) + if not version or not vercode: + raise FDroidException(_('no version information found')) + + if app.VercodeOperation: + if isinstance(app.VercodeOperation, str): + vercodes = [operate_vercode(app.VercodeOperation, vercode)] + else: + vercodes = sorted([ + operate_vercode(operation, vercode) + for operation in app.VercodeOperation + ]) + else: + vercodes = [vercode] updating = False - if version is None: - raise FDroidException(_('no version information found')) - elif vercode == app.CurrentVersionCode: + if vercodes[-1] == app.CurrentVersionCode: logging.debug("...up to date") - elif vercode > app.CurrentVersionCode: + elif vercodes[-1] > app.CurrentVersionCode: logging.debug("...updating - old vercode={0}, new vercode={1}".format( - app.CurrentVersionCode, vercode)) + app.CurrentVersionCode, vercodes[-1])) app.CurrentVersion = version - app.CurrentVersionCode = vercode + app.CurrentVersionCode = vercodes[-1] updating = True else: raise FDroidException( _('current version is newer: old vercode={old}, new vercode={new}').format( - old=app.CurrentVersionCode, new=vercode + old=app.CurrentVersionCode, new=vercodes[-1] ) ) @@ -498,35 +509,38 @@ def checkupdates_app(app): gotcur = False latest = None - for build in app.get('Builds', []): - if build.versionCode >= app.CurrentVersionCode: - gotcur = True - if not latest or build.versionCode > latest.versionCode: - latest = build + builds = app.get('Builds', []) - if latest.versionCode > app.CurrentVersionCode: - raise FDroidException( - _( - 'latest build recipe is newer: old vercode={old}, new vercode={new}' - ).format(old=latest.versionCode, new=app.CurrentVersionCode) - ) + if builds: + latest = builds[-1] + if latest.versionCode == app.CurrentVersionCode: + gotcur = True + elif latest.versionCode > app.CurrentVersionCode: + raise FDroidException( + _( + 'latest build recipe is newer: ' + 'old vercode={old}, new vercode={new}' + ).format(old=latest.versionCode, new=app.CurrentVersionCode) + ) if not gotcur: - newbuild = copy.deepcopy(latest) - newbuild.disable = False - newbuild.versionCode = app.CurrentVersionCode - newbuild.versionName = app.CurrentVersion + suffix.replace( - '%c', str(newbuild.versionCode) - ) - logging.info("...auto-generating build for " + newbuild.versionName) - if tag: - newbuild.commit = tag - else: - commit = pattern.replace('%v', str(app.CurrentVersion)) - commit = commit.replace('%c', str(newbuild.versionCode)) - newbuild.commit = commit + newbuilds = copy.deepcopy(builds[-len(vercodes):]) + for b, v in zip(newbuilds, vercodes): + b.disable = False + b.versionCode = v + b.versionName = app.CurrentVersion + suffix.replace( + '%c', str(v) + ) + logging.info("...auto-generating build for " + b.versionName) + if tag: + b.commit = tag + else: + commit = pattern.replace('%v', app.CurrentVersion) + commit = commit.replace('%c', str(v)) + b.commit = commit + + app['Builds'].extend(newbuilds) - app['Builds'].append(newbuild) name = _getappname(app) ver = _getcvname(app) commitmsg = "Update %s to %s" % (name, ver) diff --git a/fdroidserver/lint.py b/fdroidserver/lint.py index 43b101a9..c4c98143 100644 --- a/fdroidserver/lint.py +++ b/fdroidserver/lint.py @@ -262,10 +262,21 @@ def check_update_check_data_url(app): # noqa: D403 def check_vercode_operation(app): - if app.VercodeOperation and not common.VERCODE_OPERATION_RE.match( - app.VercodeOperation - ): - yield _('Invalid VercodeOperation: {field}').format(field=app.VercodeOperation) + if not app.VercodeOperation: + return + ops = ( + [app.VercodeOperation] + if isinstance(app.VercodeOperation, str) + else app.VercodeOperation + ) + invalid_ops = [] + for op in ops: + if not common.VERCODE_OPERATION_RE.match(op): + invalid_ops += op + if invalid_ops: + yield _('Invalid VercodeOperation: {invalid_ops}').format( + invalid_ops=invalid_ops + ) def check_ucm_tags(app): diff --git a/tests/checkupdates.TestCase b/tests/checkupdates.TestCase index ea59a63e..e6b2b6d9 100755 --- a/tests/checkupdates.TestCase +++ b/tests/checkupdates.TestCase @@ -56,9 +56,10 @@ class CheckupdatesTest(unittest.TestCase): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app) - build = app['Builds'][-1] - self.assertEqual(build.versionName, '1.1.9') - self.assertEqual(build.commit, '1.1.9') + + build = app['Builds'][-1] + self.assertEqual(build.versionName, '1.1.9') + self.assertEqual(build.commit, '1.1.9') with mock.patch( 'fdroidserver.checkupdates.check_http', lambda app: ('1.7.9', 10107) @@ -67,9 +68,10 @@ class CheckupdatesTest(unittest.TestCase): with mock.patch('subprocess.call', lambda cmd: 0): with self.assertRaises(FDroidException): fdroidserver.checkupdates.checkupdates_app(app) - build = app['Builds'][-1] - self.assertEqual(build.versionName, '1.1.9') - self.assertEqual(build.commit, '1.1.9') + + build = app['Builds'][-1] + self.assertEqual(build.versionName, '1.1.9') + self.assertEqual(build.commit, '1.1.9') def test_autoupdatemode_suffix(self): fdroidserver.checkupdates.options = mock.Mock() @@ -95,9 +97,60 @@ class CheckupdatesTest(unittest.TestCase): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app) - build = app['Builds'][-1] - self.assertEqual(build.versionName, '1.1.9.10109-fdroid') - self.assertEqual(build.commit, 'v1.1.9_10109') + + build = app['Builds'][-1] + self.assertEqual(build.versionName, '1.1.9.10109-fdroid') + self.assertEqual(build.commit, 'v1.1.9_10109') + + def test_autoupdate_multi_variants(self): + fdroidserver.checkupdates.options = mock.Mock() + fdroidserver.checkupdates.options.auto = 'bleh' + fdroidserver.checkupdates.config = {} + + app = fdroidserver.metadata.App() + app.id = 'loop.starts.shooting' + app.metadatapath = 'metadata/' + app.id + '.yml' + app.CurrentVersion = '1.1.8' + app.CurrentVersionCode = 101083 + app.UpdateCheckMode = 'Tags' + app.AutoUpdateMode = r'Version' + app.VercodeOperation = [ + "10*%c+1", + "10*%c+3", + ] + + build = fdroidserver.metadata.Build() + build.versionCode = app.CurrentVersionCode - 2 + build.versionName = app.CurrentVersion + build.gradle = ["arm"] + app['Builds'].append(build) + + build = fdroidserver.metadata.Build() + build.versionCode = app.CurrentVersionCode + build.versionName = app.CurrentVersion + build.gradle = ["x86"] + app['Builds'].append(build) + + with mock.patch( + 'fdroidserver.checkupdates.check_tags', + lambda app, pattern: ('1.1.9', 10109, 'v1.1.9'), + ): + with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): + with mock.patch('subprocess.call', lambda cmd: 0): + fdroidserver.checkupdates.checkupdates_app(app) + + build = app['Builds'][-2] + self.assertEqual(build.versionName, '1.1.9') + self.assertEqual(build.versionCode, 101091) + self.assertEqual(build.gradle, ["arm"]) + + build = app['Builds'][-1] + self.assertEqual(build.versionName, '1.1.9') + self.assertEqual(build.versionCode, 101093) + self.assertEqual(build.gradle, ["x86"]) + + self.assertEqual(app.CurrentVersion, '1.1.9') + self.assertEqual(app.CurrentVersionCode, 101093) def test_checkupdates_app_http(self): fdroidserver.checkupdates.options = mock.Mock() @@ -159,9 +212,10 @@ class CheckupdatesTest(unittest.TestCase): with mock.patch('fdroidserver.metadata.write_metadata', mock.Mock()): with mock.patch('subprocess.call', lambda cmd: 0): fdroidserver.checkupdates.checkupdates_app(app) - build = app['Builds'][-1] - self.assertEqual(build.versionName, '1.1.9') - self.assertEqual(build.commit, 'v1.1.9') + + build = app['Builds'][-1] + self.assertEqual(build.versionName, '1.1.9') + self.assertEqual(build.commit, 'v1.1.9') def test_check_http(self): fdroidserver.checkupdates.options = mock.Mock() @@ -178,8 +232,8 @@ class CheckupdatesTest(unittest.TestCase): respmock.read = lambda: 'v1.1.9\nc10109'.encode('utf-8') with mock.patch('urllib.request.urlopen', lambda a, b, c: respmock): vername, vercode = fdroidserver.checkupdates.check_http(app) - self.assertEqual(vername, '1.1.9') - self.assertEqual(vercode, 10109) + self.assertEqual(vername, '1.1.9') + self.assertEqual(vercode, 10109) def test_check_http_blocks_unknown_schemes(self): app = fdroidserver.metadata.App() @@ -206,7 +260,7 @@ class CheckupdatesTest(unittest.TestCase): respmock.read = lambda: 'v1.1.9-beta\nc10109'.encode('utf-8') with mock.patch('urllib.request.urlopen', lambda a, b, c: respmock): vername, vercode = fdroidserver.checkupdates.check_http(app) - self.assertEqual(vername, None) + self.assertEqual(vername, None) def test_check_tags_data(self): fdroidserver.checkupdates.options = mock.Mock() @@ -229,8 +283,8 @@ class CheckupdatesTest(unittest.TestCase): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) - self.assertEqual(vername, '1.1.9') - self.assertEqual(vercode, 10109) + self.assertEqual(vername, '1.1.9') + self.assertEqual(vercode, 10109) app.UpdateCheckData = r'b.txt|c(.*)|.|v(.*)' with mock.patch( @@ -241,8 +295,8 @@ class CheckupdatesTest(unittest.TestCase): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) - self.assertEqual(vername, '1.1.0') - self.assertEqual(vercode, 10109) + self.assertEqual(vername, '1.1.0') + self.assertEqual(vercode, 10109) app.UpdateCheckData = r'b.txt|c(.*)||' with mock.patch( @@ -253,8 +307,8 @@ class CheckupdatesTest(unittest.TestCase): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) - self.assertEqual(vername, '1.1.9') - self.assertEqual(vercode, 10109) + self.assertEqual(vername, '1.1.9') + self.assertEqual(vercode, 10109) vcs.latesttags.return_value = ['Android-1.1.0', '1.1.8'] app.UpdateCheckData = r'b.txt|c(.*)||Android-([\d.]+)' @@ -266,22 +320,22 @@ class CheckupdatesTest(unittest.TestCase): _ignored # silence the linters mock_path.is_file.return_falue = True vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) - self.assertEqual(vername, '1.1.0') - self.assertEqual(vercode, 10109) + self.assertEqual(vername, '1.1.0') + self.assertEqual(vercode, 10109) app.UpdateCheckData = r'|\+(\d+)||Android-([\d.]+)' vcs.latesttags.return_value = ['Android-1.1.0+1'] with mock.patch('fdroidserver.common.getvcs', return_value=vcs): vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) - self.assertEqual(vername, '1.1.0') - self.assertEqual(vercode, 1) + self.assertEqual(vername, '1.1.0') + self.assertEqual(vercode, 1) app.UpdateCheckData = '|||' vcs.latesttags.return_value = ['2'] with mock.patch('fdroidserver.common.getvcs', return_value=vcs): vername, vercode, _tag = fdroidserver.checkupdates.check_tags(app, None) - self.assertEqual(vername, '2') - self.assertEqual(vercode, 2) + self.assertEqual(vername, '2') + self.assertEqual(vercode, 2) if __name__ == "__main__":