Adding the option to set the default apex manifest version

Test: unit tests
Bug: 231691643
Change-Id: Iac7bca0002a13737de6680a18d5dd41696afd1fe
diff --git a/apexer/Android.bp b/apexer/Android.bp
index 20d6f27..a5bc3c6 100644
--- a/apexer/Android.bp
+++ b/apexer/Android.bp
@@ -115,6 +115,7 @@
     ],
     data: [
         ":com.android.example.apex",
+        ":com.android.example-no_version.apex",
         ":com.android.example-legacy.apex",
         ":com.android.example-logging_parent.apex",
         ":com.android.example-overridden_package_name.apex",
diff --git a/apexer/apex_manifest.py b/apexer/apex_manifest.py
index 4ee1870..0291437 100644
--- a/apexer/apex_manifest.py
+++ b/apexer/apex_manifest.py
@@ -26,13 +26,7 @@
     self.errmessage = errmessage
 
 
-def ValidateApexManifest(file):
-  try:
-    with open(file, "rb") as f:
-      manifest_pb = apex_manifest_pb2.ApexManifest()
-      manifest_pb.ParseFromString(f.read())
-  except message.DecodeError as err:
-    raise ApexManifestError(err)
+def ValidateApexManifest(manifest_pb):
   # Checking required fields
   if manifest_pb.name == "":
     raise ApexManifestError("'name' field is required.")
@@ -43,7 +37,15 @@
     raise ApexManifestError(
         "'noCode' can't be true when either preInstallHook or postInstallHook is set"
     )
-  return manifest_pb
+
+def ParseApexManifest(file):
+  try:
+    with open(file, "rb") as f:
+      manifest_pb = apex_manifest_pb2.ApexManifest()
+      manifest_pb.ParseFromString(f.read())
+      return manifest_pb
+  except message.DecodeError as err:
+    raise ApexManifestError(err)
 
 def fromApex(apexFilePath):
   with zipfile.ZipFile(apexFilePath, 'r') as apexFile:
diff --git a/apexer/apexer.py b/apexer/apexer.py
index 65ea25f..7d767a9 100644
--- a/apexer/apexer.py
+++ b/apexer/apexer.py
@@ -36,6 +36,7 @@
 import glob
 from apex_manifest import ValidateApexManifest
 from apex_manifest import ApexManifestError
+from apex_manifest import ParseApexManifest
 from manifest import android_ns
 from manifest import find_child_with_attribute
 from manifest import get_children_with_tag
@@ -187,6 +188,12 @@
           'Add testOnly=true attribute to application element in '
           'AndroidManifest file.')
   )
+  parser.add_argument(
+    '--apex_version',
+    type=int,
+    help='APEX version to use if it\'s not set in manifest'
+  )
+
 
   return parser.parse_args(argv)
 
@@ -770,14 +777,11 @@
     shutil.copyfile(src, dst)
 
   try:
-    manifest_apex = ValidateApexManifest(args.manifest)
+    manifest_apex = CreateApexManifest(args.manifest, args.apex_version)
   except ApexManifestError as err:
     print("'" + args.manifest + "' is not a valid manifest file")
     print(err.errmessage)
     return False
-  except IOError:
-    print("Cannot read manifest file: '" + args.manifest + "'")
-    return False
 
   # Create content dir and manifests dir, the manifests dir is used to
   # create the payload image
@@ -861,6 +865,15 @@
 
   return True
 
+def CreateApexManifest(manifest_path, default_version):
+  try:
+    manifest_apex = ParseApexManifest(manifest_path)
+    if default_version is not None and manifest_apex.version == 0:
+      manifest_apex.version = default_version
+    ValidateApexManifest(manifest_apex)
+    return manifest_apex
+  except IOError:
+    raise ApexManifestError("Cannot read manifest file: '" + manifest_path + "'")
 
 class TempDirectory(object):
 
diff --git a/apexer/apexer_test.py b/apexer/apexer_test.py
index da5becd..3d311d0 100644
--- a/apexer/apexer_test.py
+++ b/apexer/apexer_test.py
@@ -19,7 +19,6 @@
 import hashlib
 import logging
 import os
-import re
 import shutil
 import stat
 import subprocess
@@ -28,6 +27,7 @@
 from zipfile import ZipFile
 
 from apex_manifest import ValidateApexManifest
+from apex_manifest import ParseApexManifest
 
 logger = logging.getLogger(__name__)
 
@@ -35,6 +35,7 @@
 TEST_APEX_LEGACY = "com.android.example-legacy.apex"
 TEST_APEX_WITH_LOGGING_PARENT = "com.android.example-logging_parent.apex"
 TEST_APEX_WITH_OVERRIDDEN_PACKAGE_NAME = "com.android.example-overridden_package_name.apex"
+TEST_APEX_NO_VERSION = "com.android.example-no_version.apex"
 
 TEST_PRIVATE_KEY = os.path.join("testdata", "com.android.example.apex.pem")
 TEST_X509_KEY = os.path.join("testdata", "com.android.example.apex.x509.pem")
@@ -347,7 +348,8 @@
         cmd.extend(['--algorithm', 'SHA256_RSA4096'])
         cmd.extend(['--hash_algorithm', 'sha256'])
         cmd.extend(['--key', os.path.join(get_current_dir(), TEST_PRIVATE_KEY)])
-        manifest_apex = ValidateApexManifest(container_files["apex_manifest.pb"])
+        manifest_apex = ParseApexManifest(container_files["apex_manifest.pb"])
+        ValidateApexManifest(manifest_apex)
         cmd.extend(['--prop', 'apex.key:' + manifest_apex.name])
         # Set up the salt based on manifest content which includes name
         # and version
@@ -425,5 +427,39 @@
     def test_apex_with_overridden_package_name(self):
       self._run_build_test(TEST_APEX_WITH_OVERRIDDEN_PACKAGE_NAME)
 
+    def test_apex_default_version(self):
+        default_version = 12345
+        apex_file_path = os.path.join(get_current_dir(), TEST_APEX_NO_VERSION + ".apex")
+        container_files = self._get_container_files(apex_file_path)
+        payload_dir = self._extract_payload(apex_file_path)
+
+        manifest_pb = ParseApexManifest(container_files["apex_manifest.pb"])
+        manifest_pb.ClearField("version")
+        with open(container_files["apex_manifest.pb"], "wb") as f:
+            f.write(manifest_pb.SerializeToString())
+
+        repack_apex_file_path = self._run_apexer(container_files, payload_dir,
+                                                 ["--apex_version", str(default_version)])
+        repack_container_files = self._get_container_files(repack_apex_file_path)
+        manifest_apex = ParseApexManifest(repack_container_files["apex_manifest.pb"])
+        self.assertEqual(manifest_apex.version, default_version)
+
+    def test_apex_doesnt_override_existing_version(self):
+        default_version = 12345
+        apex_file_path = os.path.join(get_current_dir(), TEST_APEX + ".apex")
+        container_files = self._get_container_files(apex_file_path)
+        payload_dir = self._extract_payload(apex_file_path)
+
+        manifest_pb = ParseApexManifest(container_files["apex_manifest.pb"])
+        with open(container_files["apex_manifest.pb"], "wb") as f:
+            f.write(manifest_pb.SerializeToString())
+
+        repack_apex_file_path = self._run_apexer(container_files, payload_dir,
+                                                 ["--apex_version", str(default_version)])
+        repack_container_files = self._get_container_files(repack_apex_file_path)
+        manifest_apex = ParseApexManifest(repack_container_files["apex_manifest.pb"])
+        self.assertEqual(manifest_apex.version, 1)
+
+
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/apexer/testdata/Android.bp b/apexer/testdata/Android.bp
index 951f849..18e4bbe 100644
--- a/apexer/testdata/Android.bp
+++ b/apexer/testdata/Android.bp
@@ -76,3 +76,15 @@
     package_name: "com.android.overridden.example.apex",
     updatable: false,
 }
+
+apex {
+    name: "com.android.example-no_version.apex",
+    manifest: "manifest-no-version.json",
+    file_contexts: ":apex.test-file_contexts",
+    prebuilts: ["sample_prebuilt_file"],
+    key: "com.android.example.apex.key",
+    certificate: ":com.android.example.apex.certificate",
+    installable: false,
+    updatable:false,
+    generate_hashtree: false,
+}
\ No newline at end of file
diff --git a/apexer/testdata/manifest-no-version.json b/apexer/testdata/manifest-no-version.json
new file mode 100644
index 0000000..a8e50b0
--- /dev/null
+++ b/apexer/testdata/manifest-no-version.json
@@ -0,0 +1,3 @@
+{
+  "name": "com.android.example.apex"
+}