Repository: spdx/tools-python

Issue:
Add support for parsing more than one package
The current implementation only supports parsing a single package in the SPDX document. There can be one or more packages as mentioned [here](https://spdx.org/sites/cpstandard/files/pages/files/spdxversion2.1.pdf#page=18). We should add support for parsing more than one package.

Discussion:

Patch:
diff --git a/spdx/document.py b/spdx/document.py
index 1e814be..4d06870 100644
--- a/spdx/document.py
+++ b/spdx/document.py
@@ -13,6 +13,8 @@ from __future__ import absolute_import
 from __future__ import print_function
 from __future__ import unicode_literals
 
+import warnings
+
 from functools import total_ordering
 
 from spdx import config
@@ -296,7 +298,9 @@ class Document(object):
         self.comment = comment
         self.namespace = namespace
         self.creation_info = CreationInfo()
-        self.package = package
+        self.packages = []
+        if package is not None:
+            self.packages.append(package)
         self.extracted_licenses = []
         self.reviews = []
         self.annotations = []
@@ -321,6 +325,34 @@ class Document(object):
     def add_snippet(self, snip):
         self.snippet.append(snip)
 
+    def add_package(self, package):
+        self.packages.append(package)
+
+    # For backwards compatibility with older versions, we support a
+    # mode where the first package in a document may be referred to as
+    # the document's "package", and the files it contains may be
+    # referred to as the document's files.  This usage is deprecated.
+
+    @property
+    def package(self):
+        warnings.warn('document.package and document.files are deprecated; '
+                      'use document.packages instead',
+                      DeprecationWarning)
+        if len(self.packages) == 0:
+            return None
+        else:
+            return self.packages[0]
+
+    @package.setter
+    def package(self, value):
+        warnings.warn('document.package and document.files are deprecated; '
+                      'use document.packages instead',
+                      DeprecationWarning)
+        if len(self.packages) == 0:
+            self.packages.append(value)
+        else:
+            self.packages[0] = value
+
     @property
     def files(self):
         return self.package.files
@@ -345,7 +377,7 @@ class Document(object):
         messages = self.validate_namespace(messages)
         messages = self.validate_ext_document_references(messages)
         messages = self.validate_creation_info(messages)
-        messages = self.validate_package(messages)
+        messages = self.validate_packages(messages)
         messages = self.validate_extracted_licenses(messages)
         messages = self.validate_reviews(messages)
         messages = self.validate_snippet(messages)
@@ -434,11 +466,20 @@ class Document(object):
 
         return messages
 
+    def validate_packages(self, messages):
+        if len(self.packages) > 0:
+            for package in self.packages:
+                messages = package.validate(messages)
+        else:
+            messages = messages + ["Document has no packages."]
+
+        return messages
+
     def validate_package(self, messages):
         if self.package is not None:
             messages = self.package.validate(messages)
         else:
-            messages = messages + ["Document has no package."]
+            messages = messages + ["Document has no packages."]
 
         return messages
 
diff --git a/spdx/parsers/jsonyamlxmlbuilders.py b/spdx/parsers/jsonyamlxmlbuilders.py
index ee7b3f7..be5b5c8 100644
--- a/spdx/parsers/jsonyamlxmlbuilders.py
+++ b/spdx/parsers/jsonyamlxmlbuilders.py
@@ -170,12 +170,9 @@ class FileBuilder(rdfbuilders.FileBuilder):
         Raise CardinalityError if more than one.
         """
         if self.has_package(doc) and self.has_file(doc):
-            if not self.file_notice_set:
-                self.file_notice_set = True
-                self.file(doc).notice = text
-                return True
-            else:
-                raise CardinalityError("File::Notice")
+            self.file_notice_set = True
+            self.file(doc).notice = text
+            return True
         else:
             raise OrderError("File::Notice")
 
diff --git a/spdx/parsers/rdfbuilders.py b/spdx/parsers/rdfbuilders.py
index db90327..f8dae51 100644
--- a/spdx/parsers/rdfbuilders.py
+++ b/spdx/parsers/rdfbuilders.py
@@ -199,7 +199,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_chk_sum_set:
             self.package_chk_sum_set = True
-            doc.package.check_sum = checksum.Algorithm("SHA1", chk_sum)
+            doc.packages[-1].check_sum = checksum.Algorithm("SHA1", chk_sum)
         else:
             raise CardinalityError("Package::CheckSum")
 
@@ -213,7 +213,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_source_info_set:
             self.package_source_info_set = True
-            doc.package.source_info = text
+            doc.packages[-1].source_info = text
             return True
         else:
             raise CardinalityError("Package::SourceInfo")
@@ -228,7 +228,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_verif_set:
             self.package_verif_set = True
-            doc.package.verif_code = code
+            doc.packages[-1].verif_code = code
         else:
             raise CardinalityError("Package::VerificationCode")
 
@@ -238,7 +238,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         Raise OrderError if no package previously defined.
         """
         self.assert_package_exists()
-        doc.package.add_exc_file(filename)
+        doc.packages[-1].add_exc_file(filename)
 
     def set_pkg_license_comment(self, doc, text):
         """
@@ -249,7 +249,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_license_comment_set:
             self.package_license_comment_set = True
-            doc.package.license_comment = text
+            doc.packages[-1].license_comment = text
             return True
         else:
             raise CardinalityError("Package::LicenseComment")
@@ -259,7 +259,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         Set the package's attribution text.
         """
         self.assert_package_exists()
-        doc.package.attribution_text = text
+        doc.packages[-1].attribution_text = text
         return True
 
     def set_pkg_cr_text(self, doc, text):
@@ -271,7 +271,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_cr_text_set:
             self.package_cr_text_set = True
-            doc.package.cr_text = text
+            doc.packages[-1].cr_text = text
         else:
             raise CardinalityError("Package::CopyrightText")
 
@@ -284,7 +284,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_summary_set:
             self.package_summary_set = True
-            doc.package.summary = text
+            doc.packages[-1].summary = text
         else:
             raise CardinalityError("Package::Summary")
 
@@ -297,7 +297,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_desc_set:
             self.package_desc_set = True
-            doc.package.description = text
+            doc.packages[-1].description = text
         else:
             raise CardinalityError("Package::Description")
 
@@ -310,7 +310,7 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         self.assert_package_exists()
         if not self.package_comment_set:
             self.package_comment_set = True
-            doc.package.comment = text
+            doc.packages[-1].comment = text
         else:
             raise CardinalityError("Package::Comment")
 
@@ -328,12 +328,12 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
 
         if validations.validate_pkg_ext_ref_category(category):
             if (
-                len(doc.package.pkg_ext_refs)
-                and doc.package.pkg_ext_refs[-1].category is None
+                len(doc.packages[-1].pkg_ext_refs)
+                and doc.packages[-1].pkg_ext_refs[-1].category is None
             ):
-                doc.package.pkg_ext_refs[-1].category = category
+                doc.packages[-1].pkg_ext_refs[-1].category = category
             else:
-                doc.package.add_pkg_ext_refs(
+                doc.packages[-1].add_pkg_ext_refs(
                     package.ExternalPackageRef(category=category)
                 )
         else:
@@ -353,12 +353,12 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
 
         if validations.validate_pkg_ext_ref_type(typ):
             if (
-                len(doc.package.pkg_ext_refs)
-                and doc.package.pkg_ext_refs[-1].pkg_ext_ref_type is None
+                len(doc.packages[-1].pkg_ext_refs)
+                and doc.packages[-1].pkg_ext_refs[-1].pkg_ext_ref_type is None
             ):
-                doc.package.pkg_ext_refs[-1].pkg_ext_ref_type = typ
+                doc.packages[-1].pkg_ext_refs[-1].pkg_ext_ref_type = typ
             else:
-                doc.package.add_pkg_ext_refs(
+                doc.packages[-1].add_pkg_ext_refs(
                     package.ExternalPackageRef(pkg_ext_ref_type=typ)
                 )
         else:
@@ -371,11 +371,11 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder):
         Raise OrderError if no package previously defined.
         """
         self.assert_package_exists()
-        if not len(doc.package.pkg_ext_refs):
+        if not len(doc.packages[-1].pkg_ext_refs):
             raise OrderError("Package::ExternalRef")
         if not self.pkg_ext_comment_set:
             self.pkg_ext_comment_set = True
-            doc.package.pkg_ext_refs[-1].comment = comment
+            doc.packages[-1].pkg_ext_refs[-1].comment = comment
             return True
         else:
             raise CardinalityError("ExternalRef::Comment")
diff --git a/spdx/parsers/tagvaluebuilders.py b/spdx/parsers/tagvaluebuilders.py
index 64ceecc..3dd4d3c 100644
--- a/spdx/parsers/tagvaluebuilders.py
+++ b/spdx/parsers/tagvaluebuilders.py
@@ -608,12 +608,9 @@ class PackageBuilder(object):
         name - any string.
         Raise CardinalityError if package already defined.
         """
-        if not self.package_set:
-            self.package_set = True
-            doc.package = package.Package(name=name)
-            return True
-        else:
-            raise CardinalityError("Package::Name")
+        self.package_set = True
+        doc.add_package(package.Package(name=name))
+        return True
 
     def set_pkg_spdx_id(self, doc, spdx_id):
         """
@@ -624,7 +621,7 @@ class PackageBuilder(object):
         self.assert_package_exists()
         if not self.package_spdx_id_set:
             if validations.validate_pkg_spdx_id(spdx_id):
-                doc.package.spdx_id = spdx_id
+                doc.packages[-1].spdx_id = spdx_id
                 self.package_spdx_id_set = True
                 return True
             else:
@@ -642,7 +639,7 @@ class PackageBuilder(object):
         self.assert_package_exists()
         if not self.package_vers_set:
             self.package_vers_set = True
-            doc.package.version = version
+            doc.packages[-1].version = version
             return True
         else:
             raise CardinalityError("Package::Version")
@@ -657,7 +654,7 @@ class PackageBuilder(object):
         self.assert_package_exists()
         if not self.package_file_name_set:
             self.package_file_name_set = True
-            doc.package.file_name = name
+            doc.packages[-1].file_name = name
             return True
         else:
             raise CardinalityError("Package::FileName")
@@ -673,7 +670,7 @@ class PackageBuilder(object):
         if not self.package_supplier_set:
             self.package_supplier_set = True
             if validations.validate_pkg_supplier(entity):
-                doc.package.supplier = entity
+                doc.packages[-1].supplier = entity
                 return True
             else:
                 raise SPDXValueError("Package::Supplier")
@@ -691,7 +688,7 @@ class PackageBuilder(object):
         if not self.package_originator_set:
             self.package_originator_set = True
             if validations.validate_pkg_originator(entity):
-                doc.package.originator = entity
+                doc.packages[-1].originator = entity
                 return True
             else:
                 raise SPDXValueError("Package::Originator")
@@ -708,7 +705,7 @@ class PackageBuilder(object):
         self.assert_package_exists()
         if not self.package_down_location_set:
             self.package_down_location_set = True
-            doc.package.download_location = location
+            doc.packages[-1].download_location = location
             return True
         else:
             raise CardinalityError("Package::DownloadLocation")
@@ -724,8 +721,8 @@ class PackageBuilder(object):
             if files_analyzed:
                 if validations.validate_pkg_files_analyzed(files_analyzed):
                     self.package_files_analyzed_set = True
-                    doc.package.files_analyzed = files_analyzed
-                    print(doc.package.files_analyzed)
+                    doc.packages[-1].files_analyzed = files_analyzed
+                    print(doc.packages[-1].files_analyzed)
                     return True
                 else:
                     raise SPDXValueError("Package::FilesAnalyzed")
@@ -743,7 +740,7 @@ class PackageBuilder(object):
         if not self.package_home_set:
             self.package_home_set = True
             if validations.validate_pkg_homepage(location):
-                doc.package.homepage = location
+                doc.packages[-1].homepage = location
                 return True
             else:
                 raise SPDXValueError("Package::HomePage")
@@ -763,9 +760,9 @@ class PackageBuilder(object):
             self.package_verif_set = True
             match = self.VERIF_CODE_REGEX.match(code)
             if match:
-                doc.package.verif_code = match.group(self.VERIF_CODE_CODE_GRP)
+                doc.packages[-1].verif_code = match.group(self.VERIF_CODE_CODE_GRP)
                 if match.group(self.VERIF_CODE_EXC_FILES_GRP) is not None:
-                    doc.package.verif_exc_files = match.group(
+                    doc.packages[-1].verif_exc_files = match.group(
                         self.VERIF_CODE_EXC_FILES_GRP
                     ).split(",")
                 return True
@@ -784,7 +781,7 @@ class PackageBuilder(object):
         self.assert_package_exists()
         if not self.package_chk_sum_set:
             self.package_chk_sum_set = True
-            doc.package.check_sum = checksum_from_sha1(chk_sum)
+            doc.packages[-1].check_sum = checksum_from_sha1(chk_sum)
             return True
         else:
             raise CardinalityError("Package::CheckSum")
@@ -801,7 +798,7 @@ class PackageBuilder(object):
         if not self.package_source_info_set:
             self.package_source_info_set = True
             if validations.validate_pkg_src_info(text):
-                doc.package.source_info = str_from_text(text)
+                doc.packages[-1].source_info = str_from_text(text)
                 return True
             else:
                 raise SPDXValueError("Pacckage::SourceInfo")
@@ -820,7 +817,7 @@ class PackageBuilder(object):
         if not self.package_conc_lics_set:
             self.package_conc_lics_set = True
             if validations.validate_lics_conc(licenses):
-                doc.package.conc_lics = licenses
+                doc.packages[-1].conc_lics = licenses
                 return True
             else:
                 raise SPDXValueError("Package::ConcludedLicenses")
@@ -835,7 +832,7 @@ class PackageBuilder(object):
         """
         self.assert_package_exists()
         if validations.validate_lics_from_file(lic):
-            doc.package.licenses_from_files.append(lic)
+            doc.packages[-1].licenses_from_files.append(lic)
             return True
         else:
             raise SPDXValueError("Package::LicensesFromFile")
@@ -851,7 +848,7 @@ class PackageBuilder(object):
         if not self.package_license_declared_set:
             self.package_license_declared_set = True
             if validations.validate_lics_conc(lic):
-                doc.package.license_declared = lic
+                doc.packages[-1].license_declared = lic
                 return True
             else:
                 raise SPDXValueError("Package::LicenseDeclared")
@@ -869,7 +866,7 @@ class PackageBuilder(object):
         if not self.package_license_comment_set:
             self.package_license_comment_set = True
             if validations.validate_pkg_lics_comment(text):
-                doc.package.license_comment = str_from_text(text)
+                doc.packages[-1].license_comment = str_from_text(text)
                 return True
             else:
                 raise SPDXValueError("Package::LicenseComment")
@@ -883,7 +880,7 @@ class PackageBuilder(object):
         """
         self.assert_package_exists()
         if validations.validate_pkg_attribution_text(text):
-            doc.package.attribution_text = str_from_text(text)
+            doc.packages[-1].attribution_text = str_from_text(text)
             return True
         else:
             raise SPDXValueError("Package::AttributionText")
@@ -900,9 +897,9 @@ class PackageBuilder(object):
             self.package_cr_text_set = True
             if validations.validate_pkg_cr_text(text):
                 if isinstance(text, string_types):
-                    doc.package.cr_text = str_from_text(text)
+                    doc.packages[-1].cr_text = str_from_text(text)
                 else:
-                    doc.package.cr_text = text  # None or NoAssert
+                    doc.packages[-1].cr_text = text  # None or NoAssert
             else:
                 raise SPDXValueError("Package::CopyrightText")
         else:
@@ -919,7 +916,7 @@ class PackageBuilder(object):
         if not self.package_summary_set:
             self.package_summary_set = True
             if validations.validate_pkg_summary(text):
-                doc.package.summary = str_from_text(text)
+                doc.packages[-1].summary = str_from_text(text)
             else:
                 raise SPDXValueError("Package::Summary")
         else:
@@ -936,7 +933,7 @@ class PackageBuilder(object):
         if not self.package_desc_set:
             self.package_desc_set = True
             if validations.validate_pkg_desc(text):
-                doc.package.description = str_from_text(text)
+                doc.packages[-1].description = str_from_text(text)
             else:
                 raise SPDXValueError("Package::Description")
         else:
@@ -953,7 +950,7 @@ class PackageBuilder(object):
         if not self.package_comment_set:
             self.package_comment_set = True
             if validations.validate_pkg_comment(text):
-                doc.package.comment = str_from_text(text)
+                doc.packages[-1].comment = str_from_text(text)
             else:
                 raise SPDXValueError("Package::Comment")
         else:
@@ -966,12 +963,12 @@ class PackageBuilder(object):
         self.assert_package_exists()
         if validations.validate_pkg_ext_ref_category(category):
             if (
-                len(doc.package.pkg_ext_refs)
-                and doc.package.pkg_ext_refs[-1].category is None
+                len(doc.packages[-1].pkg_ext_refs)
+                and doc.packages[-1].pkg_ext_refs[-1].category is None
             ):
-                doc.package.pkg_ext_refs[-1].category = category
+                doc.packages[-1].pkg_ext_refs[-1].category = category
             else:
-                doc.package.add_pkg_ext_refs(
+                doc.packages[-1].add_pkg_ext_refs(
                     package.ExternalPackageRef(category=category)
                 )
         else:
@@ -984,12 +981,12 @@ class PackageBuilder(object):
         self.assert_package_exists()
         if validations.validate_pkg_ext_ref_type(pkg_ext_ref_type):
             if (
-                len(doc.package.pkg_ext_refs)
-                and doc.package.pkg_ext_refs[-1].pkg_ext_ref_type is None
+                len(doc.packages[-1].pkg_ext_refs)
+                and doc.packages[-1].pkg_ext_refs[-1].pkg_ext_ref_type is None
             ):
-                doc.package.pkg_ext_refs[-1].pkg_ext_ref_type = pkg_ext_ref_type
+                doc.packages[-1].pkg_ext_refs[-1].pkg_ext_ref_type = pkg_ext_ref_type
             else:
-                doc.package.add_pkg_ext_refs(
+                doc.packages[-1].add_pkg_ext_refs(
                     package.ExternalPackageRef(pkg_ext_ref_type=pkg_ext_ref_type)
                 )
         else:
@@ -1001,23 +998,23 @@ class PackageBuilder(object):
         """
         self.assert_package_exists()
         if (
-            len(doc.package.pkg_ext_refs)
-            and doc.package.pkg_ext_refs[-1].locator is None
+            len(doc.packages[-1].pkg_ext_refs)
+            and doc.packages[-1].pkg_ext_refs[-1].locator is None
         ):
-            doc.package.pkg_ext_refs[-1].locator = locator
+            doc.packages[-1].pkg_ext_refs[-1].locator = locator
         else:
-            doc.package.add_pkg_ext_refs(package.ExternalPackageRef(locator=locator))
+            doc.packages[-1].add_pkg_ext_refs(package.ExternalPackageRef(locator=locator))
 
     def add_pkg_ext_ref_comment(self, doc, comment):
         """
         Set the `comment` attribute of the `ExternalPackageRef` object.
         """
         self.assert_package_exists()
-        if not len(doc.package.pkg_ext_refs):
+        if not len(doc.packages[-1].pkg_ext_refs):
             raise OrderError("Package::ExternalRef")
         else:
             if validations.validate_pkg_ext_ref_comment(comment):
-                doc.package.pkg_ext_refs[-1].comment = str_from_text(comment)
+                doc.packages[-1].pkg_ext_refs[-1].comment = str_from_text(comment)
             else:
                 raise SPDXValueError("ExternalRef::Comment")
 
@@ -1041,7 +1038,7 @@ class FileBuilder(object):
         Raise OrderError if no package defined.
         """
         if self.has_package(doc):
-            doc.package.files.append(file.File(name))
+            doc.packages[-1].files.append(file.File(name))
             # A file name marks the start of a new file instance.
             # The builder must be reset
             # FIXME: this state does not make sense
@@ -1264,20 +1261,20 @@ class FileBuilder(object):
         """
         Return the last file in the document's package's file list.
         """
-        return doc.package.files[-1]
+        return doc.packages[-1].files[-1]
 
     def has_file(self, doc):
         """
         Return true if the document's package has at least one file.
         Does not test if the document has a package.
         """
-        return len(doc.package.files) != 0
+        return len(doc.packages[-1].files) != 0
 
     def has_package(self, doc):
         """
         Return true if the document has a package.
         """
-        return doc.package is not None
+        return len(doc.packages) != 0
 
     def reset_file_stat(self):
         """
diff --git a/spdx/writers/jsonyamlxml.py b/spdx/writers/jsonyamlxml.py
index 228352e..2ea7201 100644
--- a/spdx/writers/jsonyamlxml.py
+++ b/spdx/writers/jsonyamlxml.py
@@ -111,9 +111,8 @@ class PackageWriter(BaseWriter):
 
         return package_verification_code_object
 
-    def create_package_info(self):
+    def create_package_info(self, package):
         package_object = dict()
-        package = self.document.package
         package_object["SPDXID"] = self.spdx_id(package.spdx_id)
         package_object["name"] = package.name
         package_object["downloadLocation"] = package.download_location.__str__()
@@ -187,7 +186,7 @@ class FileWriter(BaseWriter):
 
         return artifact_of_objects
 
-    def create_file_info(self):
+    def create_file_info(self, package):
         file_types = {
             1: "fileType_source",
             2: "fileType_binary",
@@ -195,7 +194,7 @@ class FileWriter(BaseWriter):
             4: "fileType_other",
         }
         file_objects = []
-        files = self.document.files
+        files = package.files
 
         for file in files:
             file_object = dict()
@@ -476,10 +475,13 @@ class Writer(
         self.document_object["SPDXID"] = self.spdx_id(self.document.spdx_id)
         self.document_object["name"] = self.document.name
 
-        package_info_object = self.create_package_info()
-        package_info_object["files"] = self.create_file_info()
+        package_objects = []
+        for package in self.document.packages:
+            package_info_object = self.create_package_info(package)
+            package_info_object["files"] = self.create_file_info(package)
+            package_objects.append({"Package": package_info_object})
 
-        self.document_object["documentDescribes"] = [{"Package": package_info_object}]
+        self.document_object["documentDescribes"] = package_objects
 
         if self.document.has_comment:
             self.document_object["comment"] = self.document.comment
diff --git a/spdx/writers/rdf.py b/spdx/writers/rdf.py
index 3aee519..ed825ff 100644
--- a/spdx/writers/rdf.py
+++ b/spdx/writers/rdf.py
@@ -843,8 +843,8 @@ class PackageWriter(LicenseWriter):
         Return a node that represents the package in the graph.
         Call this function to write package info.
         """
-        # TODO: In the future this may be a list to support SPDX 2.0
-        return self.create_package_node(self.document.package)
+        return [self.create_package_node(x)
+                for x in self.document.packages]
 
     def handle_package_has_file_helper(self, pkg_file):
         """
@@ -1012,9 +1012,9 @@ class Writer(
             self.graph.add((doc_node, self.spdx_namespace.referencesFile, file_node))
         self.add_file_dependencies()
         # Add package
-        package_node = self.packages()
-        package_triple = (doc_node, self.spdx_namespace.describesPackage, package_node)
-        self.graph.add(package_triple)
+        for package_node in self.packages():
+            package_triple = (doc_node, self.spdx_namespace.describesPackage, package_node)
+            self.graph.add(package_triple)
         """# Add relationship
         relate_node = self.relationships()
         relate_triple = (doc_node, self.spdx_namespace.relationship, relate_node)
diff --git a/spdx/writers/tagvalue.py b/spdx/writers/tagvalue.py
index 3ef50b7..c853000 100644
--- a/spdx/writers/tagvalue.py
+++ b/spdx/writers/tagvalue.py
@@ -360,8 +360,9 @@ def write_document(document, out, validate=True):
         write_separators(out)
 
     # Write out package info
-    write_package(document.package, out)
-    write_separators(out)
+    for package in document.packages:
+        write_package(package, out)
+        write_separators(out)
 
     # Write out snippet info
     for snippet in document.snippet:


Test patch:
diff --git a/tests/test_builder.py b/tests/test_builder.py
index 4a27bef..96a469d 100644
--- a/tests/test_builder.py
+++ b/tests/test_builder.py
@@ -476,7 +476,6 @@ class TestPackageBuilder(TestCase):
         self.document = Document()
         self.entity_builder = builders.EntityBuilder()
 
-    @testing_utils.raises(builders.CardinalityError)
     def test_package_cardinality(self):
         assert self.builder.create_package(self.document, "pkg1")
         self.builder.create_package(self.document, "pkg2")
diff --git a/tests/test_document.py b/tests/test_document.py
index 073aeb7..4435ebd 100644
--- a/tests/test_document.py
+++ b/tests/test_document.py
@@ -135,6 +135,31 @@ class TestDocument(TestCase):
         assert is_valid
         assert not messages
 
+    def test_document_multiple_packages(self):
+        doc = Document(Version(2, 1), License.from_identifier('CC0-1.0'),
+                       'Sample_Document-V2.1', spdx_id='SPDXRef-DOCUMENT',
+                       namespace='https://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301')
+        doc.creation_info.add_creator(Tool('ScanCode'))
+        doc.creation_info.set_created_now()
+
+        package1 = Package(name='some/path1', download_location=NoAssert())
+        package1.spdx_id = 'SPDXRef-Package1'
+        package1.cr_text = 'Some copyrught'
+        package1.files_verified = False
+        package1.license_declared = NoAssert()
+        package1.conc_lics = NoAssert()
+        doc.add_package(package1)
+
+        package2 = Package(name='some/path2', download_location=NoAssert())
+        package2.spdx_id = 'SPDXRef-Package2'
+        package2.cr_text = 'Some copyrught'
+        package2.files_verified = False
+        package2.license_declared = NoAssert()
+        package2.conc_lics = NoAssert()
+        doc.add_package(package2)
+
+        assert len(doc.packages) == 2
+
 
 class TestWriters(TestCase):
     maxDiff = None