├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── release-please.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .pylintrc ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── entrypoint.sh ├── launch.json ├── requirements-dev.txt ├── requirements.txt ├── setup.py ├── spdxmerge ├── SPDXMerge.py ├── SPDXMergeLib.py ├── SPDX_DeepMerge.py ├── SPDX_ShallowMerge.py ├── __init__.py ├── checksum.py └── utils.py └── test ├── __init__.py ├── json ├── input │ ├── test1.json │ └── test2.json └── output │ └── result.json ├── spdx ├── input │ ├── test1.spdx │ └── test2.spdx └── output │ └── result.spdx ├── test_SPDX_DeepMerge.py └── test_SPDX_ShallowMerge.py /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "github-actions" 13 | directory: "/" 14 | schedule: 15 | interval: "weekly" 16 | - package-ecosystem: "docker" 17 | directory: "/" 18 | schedule: 19 | interval: "weekly" 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | python-version: [3.10.9] 9 | steps: 10 | - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # ratchet:actions/checkout@v4.1.3 11 | - name: Set up Python ${{ matrix.python-version }} 12 | uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # ratchet:actions/setup-python@v4 13 | with: 14 | python-version: ${{ matrix.python-version }} 15 | - name: Install dependencies 16 | run: | 17 | python -m pip install --upgrade pip 18 | pip install -r requirements.txt 19 | - name: Show help 20 | run: | 21 | python spdxmerge/SPDXMerge.py --help 22 | - name: Run pylint 23 | run: | 24 | pip install -r requirements-dev.txt 25 | pylint **/*.py 26 | - name: Run pytest 27 | run: | 28 | pytest . 29 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yml: -------------------------------------------------------------------------------- 1 | name: Create Release 2 | on: 3 | workflow_dispatch: 4 | push: 5 | branches: [main] 6 | permissions: 7 | contents: read 8 | jobs: 9 | release-please: 10 | name: Create Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: google-github-actions/release-please-action@a37ac6e4f6449ce8b3f7607e4d97d0146028dc0b # ratchet:google-github-actions/release-please-action@v4.1.0 14 | with: 15 | release-type: python 16 | token: ${{ secrets.SPDXMERGE_GITHUB_TOKEN }} 17 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create and publish signed docker image with SLSA provenance and SBOM 2 | on: 3 | push: 4 | tags: 5 | - v[0-9]+.[0-9]+.[0-9]+* 6 | env: 7 | REGISTRY_USERNAME: ${{ secrets.DOCKER_USERNAME }} 8 | REGISTRY_TOKEN: '${{ secrets.DOCKER_PASSWORD }}' 9 | DOCKER_ORGANIZATION: philipssoftware 10 | GITHUB_ORGANIZATION: philips-software 11 | KEYLESS: true 12 | jobs: 13 | get-version: 14 | name: Get version from tag 15 | runs-on: ubuntu-latest 16 | outputs: 17 | version: ${{ steps.version.outputs.version }} 18 | steps: 19 | - name: Get version 20 | id: version 21 | run: | 22 | echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT 23 | 24 | docker-image: 25 | needs: [get-version] 26 | name: Docker build from dockerfile 27 | runs-on: ubuntu-latest 28 | permissions: 29 | packages: write 30 | id-token: write 31 | container: python:3.10 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # ratchet:actions/checkout@v3 35 | - name: Show version 36 | run: echo ${{ needs.get-version.outputs.version }} 37 | - name: Build docker image 38 | uses: philips-software/docker-ci-scripts@d0045b844f08b0dcb4c62bd4acf6c36877404dae # ratchet:philips-software/docker-ci-scripts@v5.1.0 39 | with: 40 | dockerfile: . 41 | image-name: spdxmerge 42 | tags: ${{ needs.get-version.outputs.version }} latest 43 | push-branches: main 44 | push-on-git-tag: true 45 | slsa-provenance: true 46 | sign: true 47 | sbom: true 48 | 49 | pypi-executable: 50 | name: "Build and publish Python \U0001F40D distributions \U0001F4E6 to PyPI and TestPyPI" 51 | runs-on: ubuntu-latest 52 | permissions: 53 | packages: write 54 | id-token: write 55 | container: python:3.10 56 | steps: 57 | - name: Checkout code 58 | uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # ratchet:actions/checkout@v3 59 | - name: Install pypa/build 60 | run: >- 61 | python3 -m pip install build 62 | - name: Build a binary wheel and a source tarball 63 | run: >- 64 | python -m build --sdist --wheel --outdir dist/ . 65 | - name: "Publish distribution \U0001F4E6 to PyPI" 66 | if: startsWith(github.ref, 'refs/tags') 67 | uses: pypa/gh-action-pypi-publish@81e9d935c883d0b210363ab89cf05f3894778450 # ratchet:pypa/gh-action-pypi-publish@release/v1 68 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test github action for merging SPDX 2 | on: 3 | push: 4 | jobs: 5 | test-action: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@1d96c772d19495a3b5c517cd2bc0cb401ea0529f # ratchet:actions/checkout@v4.1.3 9 | - name: Test merging SPDX JSON format files 10 | uses: ./ 11 | with: 12 | docpath: ${{github.workspace}}/test/json/input 13 | name: sample-sbom-json 14 | version: "1" 15 | mergetype: "1" # 0 shallow merge, 1 deep merge 16 | author: "ci/cd build pipeline" 17 | email: "test@mail.com" 18 | docnamespace: "https://philips.example.com" 19 | filetype: "J" # J JSON, T for SPDX tag value format 20 | - name: Check result for JSON format 21 | run: | 22 | # Ignore field created since it contains a created at timestamp 23 | diff -I"created" merged-SBoM-deep.json ${{github.workspace}}/test/json/output/result.json 24 | - name: Test merging SPDX Tag value format files 25 | uses: ./ 26 | with: 27 | docpath: ${{github.workspace}}/test/spdx/input 28 | name: sample-sbom-spdx 29 | version: "1" 30 | mergetype: "0" # 0 shallow merge, 1 deep merge 31 | author: "ci/cd build pipeline" 32 | email: "test@mail.com" 33 | docnamespace: "https://philips.example.com" 34 | filetype: "T" # J JSON, T for SPDX tag value format 35 | - name: Check result for SPDX tag value format 36 | run: | 37 | # Ignore field created since it contains a created at timestamp 38 | diff -I"Created" -I"SHA[0-9]+:" merged-SBoM-shallow.spdx ${{github.workspace}}/test/spdx/output/result.spdx 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/launch.json 2 | __pycache__/ 3 | SPDXMerge.code-workspace 4 | 5 | merged-SBoM*.json 6 | 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | 4 | disable=line-too-long,missing-module-docstring,invalid-name,too-many-arguments, 5 | wrong-import-order,missing-function-docstring,duplicate-code,missing-class-docstring, 6 | redefined-builtin,fixme,attribute-defined-outside-init,import-error 7 | 8 | [REPORTS] 9 | 10 | # Set the output format. Available formats are text, parseable, colorized, msvs 11 | # (visual studio) and html. You can also give a reporter class, eg 12 | # mypackage.mymodule.MyReporterClass. 13 | output-format=colorized 14 | 15 | [FORMAT] 16 | 17 | # Maximum number of characters on a single line. 18 | max-line-length=120 19 | 20 | [TYPECHECK] 21 | signature-mutators=click.decorators.option 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.2.0](https://github.com/philips-software/SPDXMerge/compare/v0.1.3...v0.2.0) (2023-05-25) 4 | 5 | ### Features 6 | 7 | * Output path ([#42](https://github.com/philips-software/SPDXMerge/pull/42) - Thanks to @electricgull 8 | 9 | ### Bug Fixes 10 | 11 | * pylint error ([5305124](https://github.com/philips-software/SPDXMerge/commit/530512479c6741f69b6e5e500f032dcdb6e40d58)) 12 | 13 | 14 | ### Documentation 15 | 16 | * update docs ([51515d6](https://github.com/philips-software/SPDXMerge/commit/51515d6cb70f286ab68d25e359a742005097292a)) 17 | 18 | ## [0.1.3](https://github.com/philips-software/SPDXMerge/compare/v0.1.2...v0.1.3) (2023-04-26) 19 | 20 | 21 | ### Bug Fixes 22 | 23 | * remove pypi test environment ([cf37e22](https://github.com/philips-software/SPDXMerge/commit/cf37e22d43a1b7a8e076138346c5446bc1cde707)) 24 | 25 | ## [0.1.2](https://github.com/philips-software/SPDXMerge/compare/v0.1.1...v0.1.2) (2023-04-26) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * Use Trusted Publisher ([cf7d5f6](https://github.com/philips-software/SPDXMerge/commit/cf7d5f6cdfa93295b8cd0c59acb1acfa58a8692e)) 31 | 32 | ## [0.1.1](https://github.com/philips-software/SPDXMerge/compare/v0.1.0...v0.1.1) (2023-04-26) 33 | 34 | 35 | ### Bug Fixes 36 | 37 | * Also tag the latest docker-image ([17e0deb](https://github.com/philips-software/SPDXMerge/commit/17e0deb57f14ddc30d6f57bd44ce6ecc096ac556)) 38 | 39 | ## [0.1.0](https://github.com/philips-software/SPDXMerge/compare/v0.1.0...v0.1.0) (2023-04-03) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * push on tags ([8cf0f11](https://github.com/philips-software/SPDXMerge/commit/8cf0f1127243a2129d563ad7cc06ace25f87f4a9)) 45 | * Remove changelog ([7070746](https://github.com/philips-software/SPDXMerge/commit/707074687b53ac627eb3af2ca5771d3e11da324e)) 46 | * Update CHANGELOG ([97cc859](https://github.com/philips-software/SPDXMerge/commit/97cc859e7a3d36a08fd5b665097dd28806c8f639)) 47 | * update docker image name ([1e71092](https://github.com/philips-software/SPDXMerge/commit/1e71092b372bc0b558836997809e27eb12c07cfb)) 48 | 49 | 50 | ### Documentation 51 | 52 | * Add CONTRIBUTING and LICENSE ([9f7cd39](https://github.com/philips-software/SPDXMerge/commit/9f7cd39e5327b2857c00d1f1af8804e205507c7f)) 53 | 54 | 55 | ### Miscellaneous Chores 56 | 57 | * release 0.1.1 ([c75459a](https://github.com/philips-software/SPDXMerge/commit/c75459a0540f1ad479fe66ad8550fa3037b5d498)) 58 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence, 3 | # they will be requested for review when someone opens a 4 | # pull request. 5 | * @philips-software/spdx-merge 6 | 7 | # See CODEOWNERS syntax here: https://help.github.com/articles/about-codeowners/#codeowners-syntax 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to SPDXMerge 2 | 3 | - [Question or Problem?](#question) 4 | - [Issues and Bugs](#issue) 5 | 6 | ## Got a Question or Problem? 7 | Please raise an issue for now. We don't have other official communication channels in place right now. If you really want to know more, you can contact the contributors through the standard Philips communication channel. 8 | 9 | ## Found an Issue? 10 | If you find a bug in the source code or a mistake in the documentation, you can help us by submitting an issue to our [Github Repository][github]. Even better you can submit a Pull Request with a fix. 11 | 12 | [github]: https://github.com/philips-software/SPDXMerge/issues 13 | 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # For more information, please refer to https://aka.ms/vscode-docker-python 2 | FROM python:3.12.3 3 | 4 | # Assign work directory 5 | WORKDIR /app 6 | COPY . /app 7 | 8 | # Install pip requirements 9 | RUN pip install -r requirements.txt 10 | 11 | # Execute 12 | RUN chmod +x /app/entrypoint.sh 13 | ENTRYPOINT ["/app/entrypoint.sh"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Koninklijke Philips N.V. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tool for merging SPDX files 2 | 3 | This tool integrates multiple SPDX JSON formatted Software Bill of Materials (SBOMs) into a parent SBOM, either by consolidating all the contents into a single file or by creating references to multiple files. 4 | 5 | The tool works with SPDX 2.2 and SPDX 2.3 versions. 6 | 7 | ## Features 8 | 9 | Combine multiple SPDX JSON/Tag value files into a single parent Software Bill of Materials (SBOM) in one of two ways: 10 | 11 | - **Deep Merge** - Combines the contents of all SBOM files into a single comprehensive parent file, incorporating all the information about the package dependencies and their relationships. 12 | - **Shallow Merge** - Generates a parent SBOM that references multiple SBOM files in the `externalDocumentRefs` section. 13 | 14 | ## How to use 15 | 16 | ### Manual Installation 17 | 18 | SPDX Tools (`spdx-tools`) needs to be installed as a prerequisite for this application to work. It is listed in the `requirements.txt` file. 19 | Run the following command to install all necessary dependencies: 20 | 21 | ```shell 22 | pip install -r requirements.txt 23 | ``` 24 | 25 | Execute the command with the required inputs: 26 | 27 | ```shell 28 | python spdxmerge/SPDXMerge --docpath 29 | --outpath (optional) 30 | --name 31 | --version 32 | --mergetype <1 for deep merge/0 for shallow merge> 33 | --author 34 | --email 35 | --docnamespace 36 | --filetype 37 | --rootdocpath (optional) 38 | ``` 39 | --- 40 | 41 | ### 🔹 **New Update: Version Input** 42 | To establish a unique **"DESCRIBES"** relationship in the SPDX document, a **root package** must be created. 43 | For this, we now require both **name** and **version** as input parameters. 44 | 45 | --- 46 | 47 | ### 🔹 **New Update: Roothpath input** 48 | To establish a unique **"DESCRIBES"** relationship in the SPDX document, a **root document** can be specified. 49 | For this, we now introduce the **`--rootdocpath`** option to define the root document, ensuring proper validation and relationship mapping. 50 | 51 | #### Options 52 | - `--rootdocpath` (optional): Specifies the root SBoM document. 53 | 54 | #### Implementation Details 55 | - The tool scans the root document for a `DESCRIBES` relationship. 56 | - If found, the related SPDX element ID is used to establish the relationship. 57 | - If no such relationship is found, an error is raised. 58 | - The relationship is added to the master document. 59 | 60 | #### Error Handling 61 | - Raises an error if `--rootdocpath` is defined but the file is not found in the path. 62 | - Raises an error if the root document lacks a `DESCRIBES` relationship. 63 | --- 64 | 65 | ### GitHub Action 66 | 67 | ```yml 68 | - name: Checkout project 69 | uses: actions/checkout@v3 70 | - name: Run SPDX Merge tool to merge spdx files 71 | uses: philips-software/SPDXMerge@v0.2.0 72 | with: 73 | docpath: ${{github.workspace}}/Test 74 | name: sample-sbom 75 | version: 1.0.0 76 | mergetype: 1 77 | author: "Kung Fury" 78 | email: "kfury@example.com" 79 | filetype: J 80 | docnamespace: "https://mycompany.example.com" 81 | - name: Check result 82 | run: cat merged-SBoM.json 83 | ``` 84 | 85 | --- 86 | 87 | ### Docker Image 88 | 89 | ```shell 90 | docker run -it --rm \ 91 | -v $(PWD):/code \ 92 | -v $(PWD)/output/:/output \ 93 | -e DOCPATH='/code' \ 94 | -e OUTPATH='/output' \ 95 | -e NAME='' \ 96 | -e VERSION='' \ 97 | -e MERGETYPE='' \ 98 | -e AUTHOR='' \ 99 | -e EMAIL='' \ 100 | -e DOCNAMESPACE='' \ 101 | -e FILETYPE='' \ 102 | -e ROOTPATH='' \ 103 | docker.io/philipssoftware/spdxmerge:v0.2.0 104 | ``` 105 | 106 | --- 107 | 108 | ## TODOs 109 | 110 | - Option for Organization, Author tag in document creation 111 | 112 | --- 113 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'SPDX Merge' 2 | author: 'Philips' 3 | description: 'Merge one or more SBOMs into one parent SBOM' 4 | inputs: 5 | docpath: 6 | description: 'The path to the folder that contains the files to be merged' 7 | mandatory: true 8 | outpath: 9 | description: 'The path to the folder where the merged file will be saved' 10 | mandatory: false 11 | name: 12 | description: "Name of product for which SBoM is created" 13 | mandatory: true 14 | version: 15 | description: "Version of product for which SBoM is created" 16 | mandatory: true 17 | mergetype: 18 | description: "Type of merge: 0 for shallow merge, 1 for deep merge" 19 | mandatory: false 20 | default: "1" 21 | author: 22 | description: "SBoM Author name" 23 | mandatory: false 24 | email: 25 | description: "SBoM Author email address" 26 | mandatory: false 27 | docnamespace: 28 | description: "Document namespace; URL where document is stored or organzation URL" 29 | default: "https://spdx.organization.name" 30 | filetype: 31 | description: "Resulting merge file format: J for JSON, T for SPDX tag value format" 32 | default: "J" 33 | runs: 34 | using: 'docker' 35 | image: 'Dockerfile' 36 | env: 37 | DOCPATH: ${{ inputs.docpath }} 38 | NAME: ${{ inputs.name }} 39 | VERSION: ${{ inputs.version }} 40 | MERGETYPE: ${{ inputs.mergetype }} 41 | AUTHOR: ${{ inputs.author }} 42 | EMAIL: ${{ inputs.email }} 43 | DOCNAMESPACE: ${{ inputs.docnamespace }} 44 | FILETYPE: ${{ inputs.filetype }} 45 | branding: 46 | icon: 'book' 47 | color: 'blue' 48 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "arguments:" 3 | echo " - DOCPATH : $DOCPATH" 4 | echo " - OUTPATH : $OUTPATH" 5 | echo " - NAME : $NAME" 6 | echo " - VERSION : $VERSION" 7 | echo " - MERGETYPE : $MERGETYPE" 8 | echo " - AUTHOR : $AUTHOR" 9 | echo " - EMAIL : $EMAIL" 10 | echo " - DOCNAMESPACE : $DOCNAMESPACE" 11 | echo " - FILETYPE : $FILETYPE" 12 | echo " - ROOTPATH : $ROOTPATH" 13 | 14 | # TODO: add check for missing arguments 15 | python3 /app/spdxmerge/SPDXMerge.py \ 16 | --docpath "$DOCPATH" \ 17 | --outpath "$OUTPATH" \ 18 | --name "$NAME" \ 19 | --version "$VERSION" \ 20 | --mergetype $MERGETYPE \ 21 | --author "$AUTHOR" \ 22 | --email "$EMAIL" \ 23 | --docnamespace "$DOCNAMESPACE" \ 24 | --filetype "$FILETYPE" \ 25 | --rootdocpath "$ROOTPATH" -------------------------------------------------------------------------------- /launch.json: -------------------------------------------------------------------------------- 1 | python3 -u SPDXMerge.py --docpath=test/json/input -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # To ensure app dependencies are ported from your virtual environment/host machine into your container, run 'pip freeze > requirements.txt' in the terminal to overwrite this file 2 | pylint==3.1.0 3 | pytest==8.1.1 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | astroid==3.1.0 2 | beartype==0.20.0 3 | boolean.py==4.0 4 | click==8.1.8 5 | colorama==0.4.6 6 | dill==0.3.9 7 | iniconfig==2.0.0 8 | isort==5.13.2 9 | license-expression==30.4.1 10 | mccabe==0.7.0 11 | packaging==24.2 12 | platformdirs==4.3.6 13 | pluggy==1.5.0 14 | ply==3.11 15 | pylint==3.1.0 16 | pyparsing==3.2.1 17 | pytest==8.1.1 18 | PyYAML==6.0.2 19 | rdflib==7.1.3 20 | semantic-version==2.10.0 21 | spdx-tools==0.8.3 22 | tomlkit==0.13.2 23 | uritools==4.0.3 24 | xmltodict==0.14.2 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import find_packages, setup 4 | 5 | requirements_path = os.path.join(os.path.dirname(__file__), 'requirements.txt') 6 | 7 | with open(requirements_path, "r", encoding="utf8") as f: 8 | required = f.read().splitlines() 9 | 10 | setup( 11 | name='spdxmerge', 12 | version='0.2.0', 13 | description="merges content of two/more spdx sboms", 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/philips-software/SPDXMerge", 16 | packages=find_packages(include=['spdxmerge'], exclude=['test', '*.test', '*.test.*']), 17 | classifiers=[ 18 | "Programming Language :: Python :: 3", 19 | "License :: OSI Approved :: MIT License", 20 | "Operating System :: OS Independent", 21 | ], 22 | install_requires=required, 23 | python_requires='>=3.8', 24 | entry_points=''' 25 | [console_scripts] 26 | spdxmerge=spdxmerge.SPDXMerge:main 27 | ''' 28 | ) 29 | -------------------------------------------------------------------------------- /spdxmerge/SPDXMerge.py: -------------------------------------------------------------------------------- 1 | # pylint: disable=wrong-import-position 2 | import os 3 | import sys 4 | import click 5 | 6 | sys.path.insert(0, "/".join(os.path.dirname(__file__).split('/')[:-1])) 7 | 8 | from spdxmerge.SPDXMergeLib import create_merged_spdx_document, write_file 9 | from spdxmerge.utils import read_docs 10 | 11 | 12 | @click.command() 13 | @click.option("--docpath", prompt="Directory path", required=True, 14 | help="Directory path with SPDX files to be merged") 15 | @click.option("--outpath", prompt="Output directory path", required=False, prompt_required=False, 16 | help="Output directory path where merged file should be saved") 17 | @click.option("--name", prompt="Product Name", required=True, 18 | help="Name of product for which SBoM is created") 19 | @click.option("--version", prompt="Product Version", required=True, 20 | help="Version of product for which SBoM is created") 21 | @click.option("--mergetype", prompt="Shallow Merge -0 or Deep Merge-1", 22 | help="Enter 0 for shallow merge , 1 for deep merge", type=click.Choice(['0', '1']), default='1') 23 | @click.option("--author", prompt="SBoM Author name", required=True, 24 | help="Author who is writing SBoM") 25 | @click.option("--email", prompt="SBoM Author email", required=False, prompt_required=False, 26 | help="Author email address") 27 | @click.option("--docnamespace", prompt="Document namespace", 28 | help="URL where document is stored or organization URL", default="https://spdx.organization.name") 29 | @click.option("--filetype", prompt="SBoM output file type SPDX tag value format - T or JSON - J", 30 | help="Enter T for SPDX tag value format, J for JSON", 31 | type=click.Choice(['T', 't', 'J', 'j']), default='J') 32 | @click.option("--rootdocpath", prompt="SBoM root author", required=False, prompt_required=False, 33 | default="", 34 | help="SBoM that should be used as root author") 35 | def main(docpath, name, version, mergetype, author, email, docnamespace, filetype, rootdocpath=None, outpath=None): 36 | """Tool provides option to merge SPDX SBoM files. Provides two options for merging, 37 | Shallow Merge: New SBoM is created only with external ref links to SBoM files to be merged 38 | Deep Merge: New SBoM file is created by appending package, relationship, license information 39 | """ 40 | doc_list, root_doc = read_docs(docpath, rootdocpath) 41 | merge_type = "shallow" if mergetype == '0' else "deep" 42 | doc = create_merged_spdx_document(doc_list, docnamespace, name, version, author, email, merge_type, root_doc) 43 | write_file(doc, filetype, merge_type, outpath) 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /spdxmerge/SPDXMergeLib.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from spdxmerge.SPDX_DeepMerge import SPDX_DeepMerger 4 | from spdxmerge.SPDX_ShallowMerge import SPDX_ShallowMerger 5 | from spdx_tools.spdx.writer.json.json_writer import ( 6 | write_document_to_file as write_json_document, 7 | ) 8 | from spdx_tools.spdx.writer.tagvalue.tagvalue_writer import ( 9 | write_document_to_file as write_tagvalue_document 10 | ) 11 | 12 | def create_merged_spdx_document(doc_list, docnamespace, name, version, author, email, merge_type, root_doc): 13 | if merge_type == "deep": 14 | merger = SPDX_DeepMerger(doc_list, docnamespace, name, version, author, email, root_doc) 15 | merger.doc_packageinfo() 16 | merger.doc_fileinfo() 17 | merger.doc_snippetinfo() 18 | merger.doc_other_license_info() 19 | merger.doc_relationship_info() 20 | elif merge_type == "shallow": 21 | merger = SPDX_ShallowMerger(doc_list, docnamespace, name, version, author, email) 22 | merger.doc_externalDocumentRef() 23 | 24 | return merger.get_document() 25 | 26 | def write_file(doc, filetype, merge_type, outpath=None): 27 | result_filetype = "spdx" if filetype.lower() == "t" else "json" 28 | file = f"merged-SBoM-{merge_type}.{result_filetype}" 29 | if outpath: 30 | file = os.path.join(outpath, file) 31 | try: 32 | if result_filetype == "spdx": 33 | write_tagvalue_document(doc, file, validate=False) 34 | else: 35 | write_json_document(doc, file, validate=False) 36 | with open(file, 'r', encoding='utf-8') as f: 37 | json_content = json.load(f) 38 | print(json.dumps(json_content, indent=2)) 39 | except (ValueError) as e: 40 | print("Document is Invalid:", end="") 41 | print((e.args[0])) 42 | print("File "+file+" is generated") 43 | -------------------------------------------------------------------------------- /spdxmerge/SPDX_DeepMerge.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from spdx_tools.spdx.model import ( 3 | Document, 4 | Relationship, 5 | RelationshipType, 6 | CreationInfo, 7 | Actor, 8 | ActorType, 9 | Package, 10 | SpdxNoAssertion, 11 | ) 12 | 13 | 14 | class SPDX_DeepMerger: 15 | def __init__( 16 | self, 17 | doc_list=None, 18 | docnamespace=None, 19 | name=None, 20 | version=None, 21 | author=None, 22 | email=None, 23 | root_doc=None, 24 | ): 25 | self.doc_list = doc_list 26 | self.version = version 27 | self.root_doc = root_doc 28 | # data_license is "CC0-1.0" by default 29 | self.master_doc = Document( 30 | CreationInfo( 31 | spdx_version="SPDX-2.3", 32 | spdx_id="SPDXRef-DOCUMENT", 33 | name=name, 34 | document_namespace=docnamespace, 35 | creators=[ 36 | Actor(actor_type=ActorType.ORGANIZATION, name=author, email=email) 37 | ], 38 | created=datetime.utcnow().replace(microsecond=0), 39 | ) 40 | ) 41 | 42 | def get_document(self): 43 | return self.master_doc 44 | 45 | def doc_packageinfo(self): 46 | """ 47 | Append packages from document list 48 | """ 49 | if self.root_doc is None: 50 | Main_Package = Package( 51 | name=self.master_doc.creation_info.name, 52 | version=self.version, 53 | spdx_id="SPDXRef-" + str(0), 54 | download_location=SpdxNoAssertion(), 55 | ) 56 | 57 | self.master_doc.packages.append(Main_Package) 58 | for doc in self.doc_list: 59 | self.master_doc.packages += doc.packages 60 | 61 | def doc_fileinfo(self): 62 | for doc in self.doc_list: 63 | self.master_doc.files += doc.files 64 | 65 | def doc_snippetinfo(self): 66 | for doc in self.doc_list: 67 | self.master_doc.snippets += doc.snippets 68 | 69 | def doc_other_license_info(self): 70 | """ 71 | Append unique licenses to hasExtractedLicensingInfo 72 | """ 73 | master_doc_eli_ids = [] 74 | for doc in self.doc_list: 75 | doc_eli = [ 76 | eli 77 | for eli in doc.extracted_licensing_info 78 | if eli.license_id not in master_doc_eli_ids 79 | ] 80 | master_doc_eli_ids += [eli.license_id for eli in doc_eli] 81 | self.master_doc.extracted_licensing_info += doc_eli 82 | 83 | def doc_relationship_info(self): 84 | if self.root_doc is None: 85 | Main_Package = self.master_doc.packages[0] 86 | # The document should DESCRIBE the root package with name as input name and version as input version 87 | relationship = Relationship( 88 | spdx_element_id=self.master_doc.creation_info.spdx_id, 89 | relationship_type=RelationshipType.DESCRIBES, 90 | related_spdx_element_id=Main_Package.spdx_id, 91 | ) 92 | 93 | else: 94 | found = False 95 | # Find the SPDX element ID with the DESCRIBES relationship 96 | for rel in self.root_doc.relationships: 97 | if rel.relationship_type == RelationshipType.DESCRIBES: 98 | related_spdx_element_id = rel.related_spdx_element_id 99 | found = True 100 | break 101 | 102 | # If no DESCRIBES relationship is found, raise an error 103 | if not found: 104 | raise ValueError("Root document has no relationship of type DESCRIBES") 105 | 106 | relationship = Relationship( 107 | spdx_element_id=self.master_doc.creation_info.spdx_id, 108 | relationship_type=RelationshipType.DESCRIBES, 109 | related_spdx_element_id=related_spdx_element_id, 110 | ) 111 | 112 | self.master_doc.relationships.append(relationship) 113 | 114 | # Also add relationships from the imported documents 115 | valid_relationships = [] 116 | for doc in self.doc_list: 117 | for rel in doc.relationships: 118 | # Since we need a unique DESCIRBES, skip the other DESCRIBES relationships 119 | if rel.relationship_type == RelationshipType.DESCRIBES: 120 | continue 121 | 122 | valid_relationships.append(rel) 123 | 124 | # Append only valid relationships 125 | self.master_doc.relationships += valid_relationships 126 | 127 | # Since Reviews have been deprecated in SPDX 2.3, we will not include this and instead use Annotations 128 | def doc_annotation_info(self): 129 | for doc in self.doc_list: 130 | self.master_doc.annotations += doc.annotations 131 | -------------------------------------------------------------------------------- /spdxmerge/SPDX_ShallowMerge.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from spdx_tools.spdx.model import ( 3 | Checksum, 4 | ChecksumAlgorithm, 5 | Actor, 6 | ActorType, 7 | Document, 8 | ExternalDocumentRef, 9 | Package, 10 | SpdxNoAssertion, 11 | CreationInfo 12 | ) 13 | class SPDX_ShallowMerger(): 14 | def __init__(self, 15 | doc_list=None, 16 | docnamespace=None, 17 | name=None, 18 | version=None, 19 | author=None, 20 | email=None): 21 | self.doc_list = doc_list 22 | self.version = version 23 | # data_license is "CC0-1.0" by default 24 | self.master_doc = Document(CreationInfo( 25 | spdx_version="SPDX-2.3", 26 | spdx_id="SPDXRef-DOCUMENT", 27 | name=name, 28 | document_namespace=docnamespace, 29 | creators=[Actor( 30 | actor_type=ActorType.ORGANIZATION, 31 | name=author, 32 | email=email 33 | )], 34 | created=datetime.utcnow().replace(microsecond=0) 35 | )) 36 | 37 | def get_document(self): 38 | return self.master_doc 39 | 40 | def doc_externalDocumentRef(self): 41 | package = Package( 42 | name=self.master_doc.creation_info.name, 43 | version=self.version, 44 | spdx_id="SPDXRef-" + str(0), 45 | download_location=SpdxNoAssertion() 46 | ) 47 | 48 | self.master_doc.packages.append(package) 49 | 50 | # for doc in self.doc_list: 51 | # check_sum = Checksum(ChecksumAlgorithm.SHA1, doc.creation_info.document_comment) 52 | # doc_ref_id = "DocumentRef-" + doc.creation_info.spdx_id #is this how we want it? 53 | # extDoc = ExternalDocumentRef(doc_ref_id, doc.creation_info.document_namespace, check_sum) 54 | # self.master_doc.creation_info.external_document_refs.append(extDoc) 55 | for doc in self.doc_list: 56 | print(doc.comment) 57 | check_sum = Checksum(ChecksumAlgorithm.SHA1, doc.comment) 58 | doc_ref_id = "DocumentRef-" + doc.creation_info.spdx_id 59 | extDoc = ExternalDocumentRef(doc_ref_id, doc.creation_info.document_namespace, check_sum) 60 | self.master_doc.creation_info.external_document_refs.append(extDoc) 61 | -------------------------------------------------------------------------------- /spdxmerge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philips-software/SPDXMerge/488954bc4d7793d57ac594f929f9be6769e820cd/spdxmerge/__init__.py -------------------------------------------------------------------------------- /spdxmerge/checksum.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | 3 | def sha256sum(filename): 4 | h = hashlib.sha256() 5 | b = bytearray(128*1024) 6 | mv = memoryview(b) 7 | with open(filename, 'rb', buffering=0) as f: 8 | while n := f.readinto(mv): 9 | h.update(mv[:n]) 10 | return h.hexdigest() 11 | 12 | 13 | def sha1sum(filename): 14 | h = hashlib.sha1() 15 | b = bytearray(128*1024) 16 | mv = memoryview(b) 17 | with open(filename, 'rb', buffering=0) as f: 18 | while n := f.readinto(mv): 19 | h.update(mv[:n]) 20 | return h.hexdigest() 21 | -------------------------------------------------------------------------------- /spdxmerge/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from spdx_tools.spdx.parser import parse_anything 3 | from spdxmerge.checksum import sha1sum 4 | 5 | 6 | def read_docs(dir, root_doc_path=None): 7 | doc_list = [] 8 | root_doc = None 9 | doc_files = [f for f in os.listdir(dir) if f.endswith((".json", ".spdx"))] 10 | 11 | # Check if root document is present in the directory 12 | if root_doc_path and root_doc_path in doc_files: 13 | root_doc = parse_anything.parse_file(dir + "/" + root_doc_path) 14 | root_doc.comment = sha1sum(dir + "/" + root_doc_path) 15 | else: 16 | if root_doc_path != "": 17 | # Raise error if --rootdocpath is defined but given root document is not found 18 | raise FileNotFoundError( 19 | f"Root document {root_doc_path} not found in directory {dir}." 20 | ) 21 | for file in doc_files: 22 | doc = parse_anything.parse_file(dir + "/" + file) 23 | check_sum = sha1sum(dir + "/" + file) 24 | doc.comment = check_sum 25 | doc_list.append(doc) 26 | 27 | return doc_list, root_doc 28 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philips-software/SPDXMerge/488954bc4d7793d57ac594f929f9be6769e820cd/test/__init__.py -------------------------------------------------------------------------------- /test/json/input/test1.json: -------------------------------------------------------------------------------- 1 | { 2 | "SPDXID" : "SPDXRef-DOCUMENT", 3 | "spdxVersion" : "SPDX-2.3", 4 | "creationInfo" : { 5 | "created" : "2023-05-12T18:30:22Z", 6 | "creators" : [ "Person: Gary O'Neall (gary@sourceauditor.com)" ] 7 | }, 8 | "name" : "SPDX Tools v1.1.5 Java SBOM", 9 | "dataLicense" : "CC0-1.0", 10 | "documentNamespace" : "http://spdx.org/spdxdocs/tools-java/v1.1.5-444504E0-4F89-41D3-9A0C-0305E82C3301", 11 | "packages" : [ { 12 | "SPDXID" : "SPDXRef-Package", 13 | "name" : "tools-java", 14 | "versionInfo" : "1.5.1", 15 | "supplier" : "Organization: SPDX (Spdx-tech@lists.spdx.org)", 16 | "packageFileName" : "tools-java-1.1.5.zip", 17 | "checksums" : [ { 18 | "algorithm" : "SHA1", 19 | "checksumValue" : "e01bcba3c55fa7f0c82d08d0cdce9061b21bf32f" 20 | } ], 21 | "downloadLocation" : "https://github.com/spdx/tools-java/releases/download/v1.1.5/tools-java-1.1.5.zip", 22 | "filesAnalyzed" : false, 23 | "externalRefs" : [ { 24 | "referenceCategory" : "PACKAGE-MANAGER", 25 | "referenceLocator" : "pkg:github/spdx/tools-java@2235d5d7f7fe46ce1e0d54b7831c5681633b25cc", 26 | "referenceType" : "purl" 27 | } ] 28 | } ], 29 | "relationships" : [ { 30 | "spdxElementId" : "SPDXRef-DOCUMENT", 31 | "relationshipType" : "DESCRIBES", 32 | "relatedSpdxElement" : "SPDXRef-Package" 33 | } ] 34 | } -------------------------------------------------------------------------------- /test/json/input/test2.json: -------------------------------------------------------------------------------- 1 | { 2 | "SPDXID" : "SPDXRef-DOCUMENT", 3 | "spdxVersion" : "SPDX-2.3", 4 | "creationInfo" : { 5 | "created" : "2022-10-23T15:44:16Z", 6 | "creators" : [ "Person: Gary O'Neall", "Tool: spdx-maven-plugin" ], 7 | "licenseListVersion" : "3.18" 8 | }, 9 | "name" : "examplemaven", 10 | "dataLicense" : "CC0-1.0", 11 | "documentDescribes" : [ "SPDXRef-example" ], 12 | "documentNamespace" : "http://spdx.org/documents/examplemaven-0.0.1", 13 | "packages" : [ { 14 | "SPDXID" : "SPDXRef-junit", 15 | "copyrightText" : "UNSPECIFIED", 16 | "description" : "JUnit is a regression testing framework written by Erich Gamma and Kent Beck. It is used by the developer who implements unit tests in Java.", 17 | "downloadLocation" : "NOASSERTION", 18 | "filesAnalyzed" : false, 19 | "homepage" : "http://junit.org", 20 | "licenseConcluded" : "NOASSERTION", 21 | "licenseDeclared" : "CPL-1.0", 22 | "name" : "JUnit", 23 | "originator" : "Organization: JUnit", 24 | "summary" : "JUnit is a regression testing framework written by Erich Gamma and Kent Beck. It is used by the developer who implements unit tests in Java.", 25 | "versionInfo" : "3.8.1" 26 | }, { 27 | "SPDXID" : "SPDXRef-log4jslf4jbinding", 28 | "copyrightText" : "UNSPECIFIED", 29 | "description" : "The Apache Log4j SLF4J API binding to Log4j 2 Core", 30 | "downloadLocation" : "NOASSERTION", 31 | "filesAnalyzed" : false, 32 | "licenseConcluded" : "NOASSERTION", 33 | "licenseDeclared" : "NOASSERTION", 34 | "name" : "Apache Log4j SLF4J Binding", 35 | "summary" : "The Apache Log4j SLF4J API binding to Log4j 2 Core" 36 | }, { 37 | "SPDXID" : "SPDXRef-log4jslf4jApi", 38 | "copyrightText" : "UNSPECIFIED", 39 | "description" : "The slf4j API", 40 | "downloadLocation" : "NOASSERTION", 41 | "filesAnalyzed" : false, 42 | "homepage" : "http://www.slf4j.org", 43 | "licenseConcluded" : "NOASSERTION", 44 | "licenseDeclared" : "NOASSERTION", 45 | "name" : "SLF4J API Module", 46 | "summary" : "The slf4j API" 47 | }, { 48 | "SPDXID" : "SPDXRef-log4jApi", 49 | "copyrightText" : "UNSPECIFIED", 50 | "description" : "The Apache Log4j API", 51 | "downloadLocation" : "NOASSERTION", 52 | "filesAnalyzed" : false, 53 | "licenseConcluded" : "NOASSERTION", 54 | "licenseDeclared" : "NOASSERTION", 55 | "name" : "Apache Log4j API", 56 | "summary" : "The Apache Log4j API" 57 | }, { 58 | "SPDXID" : "SPDXRef-log4jImpl", 59 | "copyrightText" : "UNSPECIFIED", 60 | "description" : "The Apache Log4j Implementation", 61 | "downloadLocation" : "NOASSERTION", 62 | "filesAnalyzed" : false, 63 | "licenseConcluded" : "NOASSERTION", 64 | "licenseDeclared" : "NOASSERTION", 65 | "name" : "Apache Log4j Core", 66 | "summary" : "The Apache Log4j Implementation" 67 | }, { 68 | "SPDXID" : "SPDXRef-example", 69 | "checksums" : [ { 70 | "algorithm" : "SHA1", 71 | "checksumValue" : "b8a7e6c75001e6d78625cfc9a3103bf121abf8b4" 72 | } ], 73 | "copyrightText" : "Copyright (c) 2022 Source Auditor Inc.", 74 | "description" : "This is a simple example Maven project created using the Maven quickstart archetype with one dependency added.", 75 | "downloadLocation" : "NOASSERTION", 76 | "filesAnalyzed" : true, 77 | "homepage" : "https://github.com/spdx/spdx-examples", 78 | "licenseConcluded" : "Apache-2.0", 79 | "licenseDeclared" : "Apache-2.0", 80 | "licenseInfoFromFiles" : [ "Apache-2.0" ], 81 | "name" : "examplemaven", 82 | "originator" : "Organization: Linux Foundation", 83 | "packageFileName" : "examplemaven-0.0.1.jar", 84 | "packageVerificationCode" : { 85 | "packageVerificationCodeValue" : "c12417def36d7804096521de4280721e5863e68b" 86 | }, 87 | "primaryPackagePurpose" : "LIBRARY", 88 | "hasFiles" : [ "SPDXRef-appsource", "SPDXRef-apptest" ], 89 | "summary" : "This is a simple example Maven project created using the Maven quickstart archetype with one dependency added.", 90 | "supplier" : "Organization: SPDX", 91 | "versionInfo" : "0.0.1" 92 | } ], 93 | "files" : [ { 94 | "SPDXID" : "SPDXRef-appsource", 95 | "checksums" : [ { 96 | "algorithm" : "SHA1", 97 | "checksumValue" : "a6f47dbc7e4615058490055172fe0065c55f8fc5" 98 | } ], 99 | "copyrightText" : "Copyright (c) 2020 Source Auditor Inc.", 100 | "fileContributors" : [ "Gary O'Neall" ], 101 | "fileName" : "./src/main/java/org/spdx/examplemaven/App.java", 102 | "fileTypes" : [ "SOURCE" ], 103 | "licenseComments" : "This file contains SPDX-License-Identifiers for Apache-2.0", 104 | "licenseConcluded" : "Apache-2.0", 105 | "licenseInfoInFiles" : [ "Apache-2.0" ], 106 | "noticeText" : "SPDX-License-Identifier: Apache-2.0\nCopyright (c) 2022 Source Auditor Inc." 107 | }, { 108 | "SPDXID" : "SPDXRef-apptest", 109 | "checksums" : [ { 110 | "algorithm" : "SHA1", 111 | "checksumValue" : "4b4df52d36588c8e9482d56eebc42336447f3dad" 112 | } ], 113 | "copyrightText" : "Copyright (c) 2020 Source Auditor Inc.", 114 | "fileContributors" : [ "Gary O'Neall" ], 115 | "fileName" : "./src/test/java/org/spdx/examplemaven/AppTest.java", 116 | "fileTypes" : [ "SOURCE" ], 117 | "licenseComments" : "This file contains SPDX-License-Identifiers for Apache-2.0", 118 | "licenseConcluded" : "Apache-2.0", 119 | "licenseInfoInFiles" : [ "Apache-2.0" ], 120 | "noticeText" : "SPDX-License-Identifier: Apache-2.0\nCopyright (c) 2022 Source Auditor Inc." 121 | } ], 122 | "relationships" : [ { 123 | "spdxElementId" : "SPDXRef-junit", 124 | "relationshipType" : "TEST_DEPENDENCY_OF", 125 | "relatedSpdxElement" : "SPDXRef-example", 126 | "comment" : "Relationship created based on Maven POM information" 127 | }, { 128 | "spdxElementId" : "SPDXRef-example", 129 | "relationshipType" : "DYNAMIC_LINK", 130 | "relatedSpdxElement" : "SPDXRef-log4jslf4jbinding", 131 | "comment" : "Relationship based on Maven POM file dependency information" 132 | }, { 133 | "spdxElementId" : "SPDXRef-example", 134 | "relationshipType" : "DYNAMIC_LINK", 135 | "relatedSpdxElement" : "SPDXRef-log4jslf4jApi", 136 | "comment" : "Relationship based on Maven POM file dependency information" 137 | }, { 138 | "spdxElementId" : "SPDXRef-example", 139 | "relationshipType" : "DYNAMIC_LINK", 140 | "relatedSpdxElement" : "SPDXRef-log4jApi", 141 | "comment" : "Relationship based on Maven POM file dependency information" 142 | }, { 143 | "spdxElementId" : "SPDXRef-example", 144 | "relationshipType" : "DYNAMIC_LINK", 145 | "relatedSpdxElement" : "SPDXRef-log4jImpl", 146 | "comment" : "Relationship based on Maven POM file dependency information" 147 | }, { 148 | "spdxElementId" : "SPDXRef-appsource", 149 | "relationshipType" : "GENERATES", 150 | "relatedSpdxElement" : "SPDXRef-example", 151 | "comment" : "" 152 | }, { 153 | "spdxElementId" : "SPDXRef-apptest", 154 | "relationshipType" : "TEST_CASE_OF", 155 | "relatedSpdxElement" : "SPDXRef-example", 156 | "comment" : "" 157 | } ] 158 | } 159 | -------------------------------------------------------------------------------- /test/json/output/result.json: -------------------------------------------------------------------------------- 1 | { 2 | "SPDXID": "SPDXRef-DOCUMENT", 3 | "creationInfo": { 4 | "created": "2025-02-25T12:37:58Z", 5 | "creators": [ 6 | "Organization: ci/cd build pipeline (test@mail.com)" 7 | ] 8 | }, 9 | "dataLicense": "CC0-1.0", 10 | "name": "sample-sbom-json", 11 | "spdxVersion": "SPDX-2.3", 12 | "documentNamespace": "https://philips.example.com", 13 | "packages": [ 14 | { 15 | "SPDXID": "SPDXRef-0", 16 | "downloadLocation": "NOASSERTION", 17 | "filesAnalyzed": true, 18 | "name": "sample-sbom-json", 19 | "versionInfo": "1" 20 | }, 21 | { 22 | "SPDXID": "SPDXRef-Package", 23 | "checksums": [ 24 | { 25 | "algorithm": "SHA1", 26 | "checksumValue": "e01bcba3c55fa7f0c82d08d0cdce9061b21bf32f" 27 | } 28 | ], 29 | "downloadLocation": "https://github.com/spdx/tools-java/releases/download/v1.1.5/tools-java-1.1.5.zip", 30 | "externalRefs": [ 31 | { 32 | "referenceCategory": "PACKAGE_MANAGER", 33 | "referenceLocator": "pkg:github/spdx/tools-java@2235d5d7f7fe46ce1e0d54b7831c5681633b25cc", 34 | "referenceType": "purl" 35 | } 36 | ], 37 | "filesAnalyzed": false, 38 | "name": "tools-java", 39 | "packageFileName": "tools-java-1.1.5.zip", 40 | "supplier": "Organization: SPDX (Spdx-tech@lists.spdx.org)", 41 | "versionInfo": "1.5.1" 42 | }, 43 | { 44 | "SPDXID": "SPDXRef-junit", 45 | "copyrightText": "UNSPECIFIED", 46 | "description": "JUnit is a regression testing framework written by Erich Gamma and Kent Beck. It is used by the developer who implements unit tests in Java.", 47 | "downloadLocation": "NOASSERTION", 48 | "filesAnalyzed": false, 49 | "homepage": "http://junit.org", 50 | "licenseConcluded": "NOASSERTION", 51 | "licenseDeclared": "CPL-1.0", 52 | "name": "JUnit", 53 | "originator": "Organization: JUnit", 54 | "summary": "JUnit is a regression testing framework written by Erich Gamma and Kent Beck. It is used by the developer who implements unit tests in Java.", 55 | "versionInfo": "3.8.1" 56 | }, 57 | { 58 | "SPDXID": "SPDXRef-log4jslf4jbinding", 59 | "copyrightText": "UNSPECIFIED", 60 | "description": "The Apache Log4j SLF4J API binding to Log4j 2 Core", 61 | "downloadLocation": "NOASSERTION", 62 | "filesAnalyzed": false, 63 | "licenseConcluded": "NOASSERTION", 64 | "licenseDeclared": "NOASSERTION", 65 | "name": "Apache Log4j SLF4J Binding", 66 | "summary": "The Apache Log4j SLF4J API binding to Log4j 2 Core" 67 | }, 68 | { 69 | "SPDXID": "SPDXRef-log4jslf4jApi", 70 | "copyrightText": "UNSPECIFIED", 71 | "description": "The slf4j API", 72 | "downloadLocation": "NOASSERTION", 73 | "filesAnalyzed": false, 74 | "homepage": "http://www.slf4j.org", 75 | "licenseConcluded": "NOASSERTION", 76 | "licenseDeclared": "NOASSERTION", 77 | "name": "SLF4J API Module", 78 | "summary": "The slf4j API" 79 | }, 80 | { 81 | "SPDXID": "SPDXRef-log4jApi", 82 | "copyrightText": "UNSPECIFIED", 83 | "description": "The Apache Log4j API", 84 | "downloadLocation": "NOASSERTION", 85 | "filesAnalyzed": false, 86 | "licenseConcluded": "NOASSERTION", 87 | "licenseDeclared": "NOASSERTION", 88 | "name": "Apache Log4j API", 89 | "summary": "The Apache Log4j API" 90 | }, 91 | { 92 | "SPDXID": "SPDXRef-log4jImpl", 93 | "copyrightText": "UNSPECIFIED", 94 | "description": "The Apache Log4j Implementation", 95 | "downloadLocation": "NOASSERTION", 96 | "filesAnalyzed": false, 97 | "licenseConcluded": "NOASSERTION", 98 | "licenseDeclared": "NOASSERTION", 99 | "name": "Apache Log4j Core", 100 | "summary": "The Apache Log4j Implementation" 101 | }, 102 | { 103 | "SPDXID": "SPDXRef-example", 104 | "checksums": [ 105 | { 106 | "algorithm": "SHA1", 107 | "checksumValue": "b8a7e6c75001e6d78625cfc9a3103bf121abf8b4" 108 | } 109 | ], 110 | "copyrightText": "Copyright (c) 2022 Source Auditor Inc.", 111 | "description": "This is a simple example Maven project created using the Maven quickstart archetype with one dependency added.", 112 | "downloadLocation": "NOASSERTION", 113 | "filesAnalyzed": true, 114 | "homepage": "https://github.com/spdx/spdx-examples", 115 | "licenseConcluded": "Apache-2.0", 116 | "licenseDeclared": "Apache-2.0", 117 | "licenseInfoFromFiles": [ 118 | "Apache-2.0" 119 | ], 120 | "name": "examplemaven", 121 | "originator": "Organization: Linux Foundation", 122 | "packageFileName": "examplemaven-0.0.1.jar", 123 | "packageVerificationCode": { 124 | "packageVerificationCodeValue": "c12417def36d7804096521de4280721e5863e68b" 125 | }, 126 | "primaryPackagePurpose": "LIBRARY", 127 | "summary": "This is a simple example Maven project created using the Maven quickstart archetype with one dependency added.", 128 | "supplier": "Organization: SPDX", 129 | "versionInfo": "0.0.1" 130 | } 131 | ], 132 | "files": [ 133 | { 134 | "SPDXID": "SPDXRef-appsource", 135 | "checksums": [ 136 | { 137 | "algorithm": "SHA1", 138 | "checksumValue": "a6f47dbc7e4615058490055172fe0065c55f8fc5" 139 | } 140 | ], 141 | "copyrightText": "Copyright (c) 2020 Source Auditor Inc.", 142 | "fileContributors": [ 143 | "Gary O'Neall" 144 | ], 145 | "fileName": "./src/main/java/org/spdx/examplemaven/App.java", 146 | "fileTypes": [ 147 | "SOURCE" 148 | ], 149 | "licenseComments": "This file contains SPDX-License-Identifiers for Apache-2.0", 150 | "licenseConcluded": "Apache-2.0", 151 | "licenseInfoInFiles": [ 152 | "Apache-2.0" 153 | ], 154 | "noticeText": "SPDX-License-Identifier: Apache-2.0\nCopyright (c) 2022 Source Auditor Inc." 155 | }, 156 | { 157 | "SPDXID": "SPDXRef-apptest", 158 | "checksums": [ 159 | { 160 | "algorithm": "SHA1", 161 | "checksumValue": "4b4df52d36588c8e9482d56eebc42336447f3dad" 162 | } 163 | ], 164 | "copyrightText": "Copyright (c) 2020 Source Auditor Inc.", 165 | "fileContributors": [ 166 | "Gary O'Neall" 167 | ], 168 | "fileName": "./src/test/java/org/spdx/examplemaven/AppTest.java", 169 | "fileTypes": [ 170 | "SOURCE" 171 | ], 172 | "licenseComments": "This file contains SPDX-License-Identifiers for Apache-2.0", 173 | "licenseConcluded": "Apache-2.0", 174 | "licenseInfoInFiles": [ 175 | "Apache-2.0" 176 | ], 177 | "noticeText": "SPDX-License-Identifier: Apache-2.0\nCopyright (c) 2022 Source Auditor Inc." 178 | } 179 | ], 180 | "relationships": [ 181 | { 182 | "spdxElementId": "SPDXRef-DOCUMENT", 183 | "relatedSpdxElement": "SPDXRef-0", 184 | "relationshipType": "DESCRIBES" 185 | }, 186 | { 187 | "spdxElementId": "SPDXRef-junit", 188 | "comment": "Relationship created based on Maven POM information", 189 | "relatedSpdxElement": "SPDXRef-example", 190 | "relationshipType": "TEST_DEPENDENCY_OF" 191 | }, 192 | { 193 | "spdxElementId": "SPDXRef-example", 194 | "comment": "Relationship based on Maven POM file dependency information", 195 | "relatedSpdxElement": "SPDXRef-log4jslf4jbinding", 196 | "relationshipType": "DYNAMIC_LINK" 197 | }, 198 | { 199 | "spdxElementId": "SPDXRef-example", 200 | "comment": "Relationship based on Maven POM file dependency information", 201 | "relatedSpdxElement": "SPDXRef-log4jslf4jApi", 202 | "relationshipType": "DYNAMIC_LINK" 203 | }, 204 | { 205 | "spdxElementId": "SPDXRef-example", 206 | "comment": "Relationship based on Maven POM file dependency information", 207 | "relatedSpdxElement": "SPDXRef-log4jApi", 208 | "relationshipType": "DYNAMIC_LINK" 209 | }, 210 | { 211 | "spdxElementId": "SPDXRef-example", 212 | "comment": "Relationship based on Maven POM file dependency information", 213 | "relatedSpdxElement": "SPDXRef-log4jImpl", 214 | "relationshipType": "DYNAMIC_LINK" 215 | }, 216 | { 217 | "spdxElementId": "SPDXRef-appsource", 218 | "comment": "", 219 | "relatedSpdxElement": "SPDXRef-example", 220 | "relationshipType": "GENERATES" 221 | }, 222 | { 223 | "spdxElementId": "SPDXRef-apptest", 224 | "comment": "", 225 | "relatedSpdxElement": "SPDXRef-example", 226 | "relationshipType": "TEST_CASE_OF" 227 | }, 228 | { 229 | "spdxElementId": "SPDXRef-example", 230 | "relatedSpdxElement": "SPDXRef-appsource", 231 | "relationshipType": "CONTAINS" 232 | }, 233 | { 234 | "spdxElementId": "SPDXRef-example", 235 | "relatedSpdxElement": "SPDXRef-apptest", 236 | "relationshipType": "CONTAINS" 237 | } 238 | ] 239 | } -------------------------------------------------------------------------------- /test/spdx/input/test1.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.2 2 | DataLicense: CC0-1.0 3 | SPDXID: SPDXRef-DOCUMENT 4 | DocumentName: Test document for SPDX tag value format 5 | DocumentNamespace: https://spdx.org/510f728d-05b2-4e32-8501-550587c39a2a 6 | LicenseListVersion: 3.8 7 | 8 | ## Creation information 9 | Creator: Organization: Test 10 | Creator: Tool: null 11 | Created: 2023-05-12T18:30:22Z 12 | CreatorComment: This SPDX file was generated by null. 13 | DocumentComment: Test 14 | 15 | ## Start of pub package 'glob' version 1.2.0 16 | PackageName: glob 17 | SPDXID: SPDXRef-A9 18 | PackageVersion: 1.2.0 19 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/glob/glob@1.2.0 20 | PackageSupplier: Organization: glob 21 | PackageOriginator: NOASSERTION 22 | PackageDownloadLocation: git+https://github.com/dart-lang/glob.git@1.2.0 23 | FilesAnalyzed: true 24 | PackageHomePage: https://github.com/dart-lang/glob 25 | PackageLicenseConcluded: BSD-3-Clause 26 | PackageLicenseDeclared: NOASSERTION 27 | PackageLicenseInfoFromFiles: BSD-3-Clause 28 | PackageCopyrightText: NOASSERTION 29 | PackageDescription: Bash-style filename globbing. 30 | Relationship: SPDXRef-A9 DYNAMIC_LINK SPDXRef-A4 31 | Relationship: SPDXRef-A9 DYNAMIC_LINK SPDXRef-A15 32 | Relationship: SPDXRef-A9 DYNAMIC_LINK SPDXRef-A13 33 | Relationship: SPDXRef-A9 DYNAMIC_LINK SPDXRef-A17 34 | 35 | ## Start of pub package 'archive' version 2.0.13 36 | PackageName: archive 37 | SPDXID: SPDXRef-A2 38 | PackageVersion: 2.0.13 39 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/archive/archive@2.0.13 40 | PackageSupplier: Organization: archive 41 | PackageOriginator: NOASSERTION 42 | PackageDownloadLocation: git+https://github.com/brendan-duncan/archive.git@2.0.13 43 | FilesAnalyzed: true 44 | PackageHomePage: https://github.com/brendan-duncan/archive 45 | PackageLicenseConcluded: Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND MIT 46 | PackageLicenseDeclared: NOASSERTION 47 | PackageLicenseInfoFromFiles: Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND MIT 48 | PackageCopyrightText: NOASSERTION 49 | PackageDescription: Provides encoders and decoders for various archive and compression formats such as zip, tar, bzip2, gzip, and zlib. 50 | Relationship: SPDXRef-A2 DYNAMIC_LINK SPDXRef-A8 51 | 52 | ## Start of pub package 'args' version 1.6.0 53 | PackageName: args 54 | SPDXID: SPDXRef-A3 55 | PackageVersion: 1.6.0 56 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/args/args@1.6.0 57 | PackageSupplier: Organization: args 58 | PackageOriginator: NOASSERTION 59 | PackageDownloadLocation: git+https://github.com/dart-lang/args.git@1.6.0 60 | FilesAnalyzed: true 61 | PackageHomePage: https://github.com/dart-lang/args 62 | PackageLicenseConcluded: BSD-3-Clause 63 | PackageLicenseDeclared: NOASSERTION 64 | PackageLicenseInfoFromFiles: BSD-3-Clause 65 | PackageCopyrightText: NOASSERTION 66 | PackageDescription: Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options. 67 | 68 | ## Start of pub package 'meta' version 1.1.8 69 | PackageName: meta 70 | SPDXID: SPDXRef-A11 71 | PackageVersion: 1.1.8 72 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/meta/meta@1.1.8 73 | PackageSupplier: Organization: meta 74 | PackageOriginator: NOASSERTION 75 | PackageDownloadLocation: git+https://github.com/dart-lang/sdk.git@master#pkg%2Fmeta 76 | FilesAnalyzed: true 77 | PackageHomePage: https://github.com/dart-lang/sdk/tree/master/pkg/meta 78 | PackageLicenseConcluded: BSD-3-Clause 79 | PackageLicenseDeclared: NOASSERTION 80 | PackageLicenseInfoFromFiles: BSD-3-Clause 81 | PackageCopyrightText: NOASSERTION 82 | PackageDescription: This library contains the definitions of annotations that provide additional semantic information about the program being annotated. These annotations are intended to be used by tools to provide a better user experience. 83 | 84 | 85 | ## Start of pub package 'string_scanner' version 1.0.5 86 | PackageName: string_scanner 87 | SPDXID: SPDXRef-A17 88 | PackageVersion: 1.0.5 89 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/string_scanner/string_scanner@1.0.5 90 | PackageSupplier: Organization: string_scanner 91 | PackageOriginator: NOASSERTION 92 | PackageDownloadLocation: git+https://github.com/dart-lang/string_scanner.git@1.0.5 93 | FilesAnalyzed: true 94 | PackageHomePage: https://github.com/dart-lang/string_scanner 95 | PackageLicenseConcluded: BSD-3-Clause 96 | PackageLicenseDeclared: NOASSERTION 97 | PackageLicenseInfoFromFiles: BSD-3-Clause 98 | PackageCopyrightText: NOASSERTION 99 | PackageDescription: A class for parsing strings using a sequence of patterns. 100 | Relationship: SPDXRef-A17 DYNAMIC_LINK SPDXRef-A11 101 | Relationship: SPDXRef-A17 DYNAMIC_LINK SPDXRef-A16 102 | Relationship: SPDXRef-A17 DYNAMIC_LINK SPDXRef-A5 103 | 104 | ## Start of pub package 'node_interop' version 1.1.1 105 | PackageName: node_interop 106 | SPDXID: SPDXRef-A12 107 | PackageVersion: 1.1.1 108 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/node_interop/node_interop@1.1.1 109 | PackageSupplier: Organization: node_interop 110 | PackageOriginator: NOASSERTION 111 | PackageDownloadLocation: git+https://github.com/pulyaevskiy/node-interop.git@1.1.1 112 | FilesAnalyzed: true 113 | PackageHomePage: https://github.com/pulyaevskiy/node-interop 114 | PackageLicenseConcluded: BSD-3-Clause 115 | PackageLicenseDeclared: NOASSERTION 116 | PackageLicenseInfoFromFiles: BSD-3-Clause 117 | PackageCopyrightText: NOASSERTION 118 | PackageDescription: Provides Dart bindings and utility functions for core Node.js modules. 119 | Relationship: SPDXRef-A12 DYNAMIC_LINK SPDXRef-A10 120 | 121 | ## Start of pub package 'charcode' version 1.1.3 122 | PackageName: charcode 123 | SPDXID: SPDXRef-A5 124 | PackageVersion: 1.1.3 125 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/charcode/charcode@1.1.3 126 | PackageSupplier: Organization: charcode 127 | PackageOriginator: NOASSERTION 128 | PackageDownloadLocation: git+https://github.com/dart-lang/charcode.git@1.1.3 129 | FilesAnalyzed: true 130 | PackageHomePage: https://github.com/dart-lang/charcode 131 | PackageLicenseConcluded: BSD-3-Clause 132 | PackageLicenseDeclared: NOASSERTION 133 | PackageLicenseInfoFromFiles: BSD-3-Clause 134 | PackageCopyrightText: NOASSERTION 135 | PackageDescription: Constants for ASCII and common non-ASCII character codes represented by top-level constants. 136 | 137 | ## Start of pub package 'pedantic' version 1.9.0 138 | PackageName: pedantic 139 | SPDXID: SPDXRef-A15 140 | PackageVersion: 1.9.0 141 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/pedantic/pedantic@1.9.0 142 | PackageSupplier: Organization: pedantic 143 | PackageOriginator: NOASSERTION 144 | PackageDownloadLocation: git+https://github.com/dart-lang/pedantic.git@1.9.0 145 | FilesAnalyzed: true 146 | PackageHomePage: https://github.com/dart-lang/pedantic 147 | PackageLicenseConcluded: BSD-3-Clause 148 | PackageLicenseDeclared: NOASSERTION 149 | PackageLicenseInfoFromFiles: BSD-3-Clause 150 | PackageCopyrightText: NOASSERTION 151 | PackageDescription: How to get the most value from Dart static analysis. 152 | 153 | ## Start of pub package 'convert' version 2.1.1 154 | PackageName: convert 155 | SPDXID: SPDXRef-A7 156 | PackageVersion: 2.1.1 157 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/convert/convert@2.1.1 158 | PackageSupplier: Organization: convert 159 | PackageOriginator: NOASSERTION 160 | PackageDownloadLocation: git+https://github.com/dart-lang/convert.git@2.1.1 161 | FilesAnalyzed: true 162 | PackageHomePage: https://github.com/dart-lang/convert 163 | PackageLicenseConcluded: BSD-3-Clause 164 | PackageLicenseDeclared: NOASSERTION 165 | PackageLicenseInfoFromFiles: BSD-3-Clause 166 | PackageCopyrightText: NOASSERTION 167 | PackageDescription: Utilities for converting between data representations. 168 | Relationship: SPDXRef-A7 DYNAMIC_LINK SPDXRef-A19 169 | 170 | ## Start of pub package 'source_span' version 1.7.0 171 | PackageName: source_span 172 | SPDXID: SPDXRef-A16 173 | PackageVersion: 1.7.0 174 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/source_span/source_span@1.7.0 175 | PackageSupplier: Organization: source_span 176 | PackageOriginator: NOASSERTION 177 | PackageDownloadLocation: git+https://github.com/dart-lang/source_span.git@1.7.0 178 | FilesAnalyzed: true 179 | PackageHomePage: https://github.com/dart-lang/source_span 180 | PackageLicenseConcluded: BSD-3-Clause 181 | PackageLicenseDeclared: NOASSERTION 182 | PackageLicenseInfoFromFiles: BSD-3-Clause 183 | PackageCopyrightText: NOASSERTION 184 | PackageDescription: A library for identifying source spans and locations. 185 | Relationship: SPDXRef-A16 DYNAMIC_LINK SPDXRef-A18 186 | 187 | ## Start of pub package 'js' version 0.6.1+1 188 | PackageName: js 189 | SPDXID: SPDXRef-A10 190 | PackageVersion: 0.6.1+1 191 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/js/js@0.6.1%2B1 192 | PackageSupplier: Organization: js 193 | PackageOriginator: NOASSERTION 194 | PackageDownloadLocation: git+https://github.com/dart-lang/sdk.git@master#pkg%2Fjs 195 | FilesAnalyzed: true 196 | PackageHomePage: https://github.com/dart-lang/sdk/tree/master/pkg/js 197 | PackageLicenseConcluded: BSD-3-Clause 198 | PackageLicenseDeclared: NOASSERTION 199 | PackageLicenseInfoFromFiles: BSD-3-Clause 200 | PackageCopyrightText: NOASSERTION 201 | PackageDescription: Access JavaScript from Dart. 202 | 203 | ## Start of pub package 'test' version 204 | PackageName: Test 205 | SPDXID: SPDXRef-A1 206 | PackageVersion: 207 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/test/test@ 208 | PackageSupplier: Organization: test 209 | PackageOriginator: NOASSERTION 210 | PackageDownloadLocation: git+https://github.com/dart-lang/sdk.git@master#pkg%2Fjs 211 | FilesAnalyzed: true 212 | PackageHomePage: NOASSERTION 213 | PackageLicenseConcluded: NOASSERTION 214 | PackageLicenseDeclared: NOASSERTION 215 | PackageLicenseInfoFromFiles: NOASSERTION 216 | PackageCopyrightText: NOASSERTION 217 | Relationship: SPDXRef-A1 DYNAMIC_LINK SPDXRef-A9 218 | Relationship: SPDXRef-A1 DYNAMIC_LINK SPDXRef-A3 219 | Relationship: SPDXRef-A1 DYNAMIC_LINK SPDXRef-A2 220 | Relationship: SPDXRef-A1 DYNAMIC_LINK SPDXRef-A14 221 | 222 | ## Start of pub package 'term_glyph' version 1.1.0 223 | PackageName: term_glyph 224 | SPDXID: SPDXRef-A18 225 | PackageVersion: 1.1.0 226 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/term_glyph/term_glyph@1.1.0 227 | PackageSupplier: Organization: term_glyph 228 | PackageOriginator: NOASSERTION 229 | PackageDownloadLocation: git+https://github.com/dart-lang/term_glyph.git@1.1.0 230 | FilesAnalyzed: true 231 | PackageHomePage: https://github.com/dart-lang/term_glyph 232 | PackageLicenseConcluded: BSD-3-Clause 233 | PackageLicenseDeclared: NOASSERTION 234 | PackageLicenseInfoFromFiles: BSD-3-Clause 235 | PackageCopyrightText: NOASSERTION 236 | PackageDescription: Useful Unicode glyphs and ASCII substitutes. 237 | 238 | ## Start of pub package 'async' version 2.4.1 239 | PackageName: async 240 | SPDXID: SPDXRef-A4 241 | PackageVersion: 2.4.1 242 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/async/async@2.4.1 243 | PackageSupplier: Organization: async 244 | PackageOriginator: NOASSERTION 245 | PackageDownloadLocation: git+https://github.com/dart-lang/async.git@2.4.1 246 | FilesAnalyzed: true 247 | PackageHomePage: https://www.github.com/dart-lang/async 248 | PackageLicenseConcluded: BSD-3-Clause 249 | PackageLicenseDeclared: NOASSERTION 250 | PackageLicenseInfoFromFiles: BSD-3-Clause 251 | PackageCopyrightText: NOASSERTION 252 | PackageDescription: Utility functions and classes related to the 'dart:async' library. 253 | Relationship: SPDXRef-A4 DYNAMIC_LINK SPDXRef-A6 254 | 255 | ## Start of pub package 'path' version 1.7.0 256 | PackageName: path 257 | SPDXID: SPDXRef-A14 258 | PackageVersion: 1.7.0 259 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/path/path@1.7.0 260 | PackageSupplier: Organization: path 261 | PackageOriginator: NOASSERTION 262 | PackageDownloadLocation: git+https://github.com/dart-lang/path.git@1.7.0 263 | FilesAnalyzed: true 264 | PackageHomePage: https://github.com/dart-lang/path 265 | PackageLicenseConcluded: BSD-3-Clause 266 | PackageLicenseDeclared: NOASSERTION 267 | PackageLicenseInfoFromFiles: BSD-3-Clause 268 | PackageCopyrightText: NOASSERTION 269 | PackageDescription: A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web. 270 | 271 | ## Start of pub package 'crypto' version 2.1.4 272 | PackageName: crypto 273 | SPDXID: SPDXRef-A8 274 | PackageVersion: 2.1.4 275 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/crypto/crypto@2.1.4 276 | PackageSupplier: Organization: crypto 277 | PackageOriginator: NOASSERTION 278 | PackageDownloadLocation: git+https://github.com/dart-lang/crypto.git@2.1.4 279 | FilesAnalyzed: true 280 | PackageHomePage: https://www.github.com/dart-lang/crypto 281 | PackageLicenseConcluded: BSD-3-Clause 282 | PackageLicenseDeclared: NOASSERTION 283 | PackageLicenseInfoFromFiles: BSD-3-Clause 284 | PackageCopyrightText: NOASSERTION 285 | PackageDescription: Library of cryptographic functions. 286 | Relationship: SPDXRef-A8 DYNAMIC_LINK SPDXRef-A7 287 | 288 | ## Start of pub package 'collection' version 1.14.12 289 | PackageName: collection 290 | SPDXID: SPDXRef-A6 291 | PackageVersion: 1.14.12 292 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/collection/collection@1.14.12 293 | PackageSupplier: Organization: collection 294 | PackageOriginator: NOASSERTION 295 | PackageDownloadLocation: git+https://github.com/dart-lang/collection.git@1.14.12 296 | FilesAnalyzed: true 297 | PackageHomePage: https://www.github.com/dart-lang/collection 298 | PackageLicenseConcluded: BSD-3-Clause 299 | PackageLicenseDeclared: NOASSERTION 300 | PackageLicenseInfoFromFiles: BSD-3-Clause 301 | PackageCopyrightText: NOASSERTION 302 | PackageDescription: Collections and utilities functions and classes related to collections. 303 | 304 | ## Start of pub package 'typed_data' version 1.1.6 305 | PackageName: typed_data 306 | SPDXID: SPDXRef-A19 307 | PackageVersion: 1.1.6 308 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/typed_data/typed_data@1.1.6 309 | PackageSupplier: Organization: typed_data 310 | PackageOriginator: NOASSERTION 311 | PackageDownloadLocation: git+https://github.com/dart-lang/typed_data.git@1.1.6 312 | FilesAnalyzed: true 313 | PackageHomePage: https://github.com/dart-lang/typed_data 314 | PackageLicenseConcluded: BSD-3-Clause 315 | PackageLicenseDeclared: NOASSERTION 316 | PackageLicenseInfoFromFiles: BSD-3-Clause 317 | PackageCopyrightText: NOASSERTION 318 | PackageDescription: Utility functions and classes related to the 'dart:typed_data' library. 319 | 320 | ## Start of pub package 'node_io' version 1.1.1 321 | PackageName: node_io 322 | SPDXID: SPDXRef-A13 323 | PackageVersion: 1.1.1 324 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/node_io/node_io@1.1.1 325 | PackageSupplier: Organization: node_io 326 | PackageOriginator: NOASSERTION 327 | PackageDownloadLocation: git+https://github.com/pulyaevskiy/node-interop.git@1.1.1 328 | FilesAnalyzed: true 329 | PackageHomePage: https://github.com/pulyaevskiy/node-interop 330 | PackageLicenseConcluded: BSD-3-Clause 331 | PackageLicenseDeclared: NOASSERTION 332 | PackageLicenseInfoFromFiles: BSD-3-Clause 333 | PackageCopyrightText: NOASSERTION 334 | PackageDescription: Like dart:io but with Node.js. 335 | Relationship: SPDXRef-A13 DYNAMIC_LINK SPDXRef-A12 336 | 337 | -------------------------------------------------------------------------------- /test/spdx/input/test2.spdx: -------------------------------------------------------------------------------- 1 | SPDXVersion: SPDX-2.2 2 | DataLicense: CC0-1.0 3 | SPDXID: SPDXRef-DOCUMENT 4 | DocumentName: Test document for SPDX tag value format 5 | DocumentNamespace: https://spdx.org/510f728d-05b2-4e32-8501-550587c39a2a 6 | LicenseListVersion: 3.8 7 | 8 | ## Creation information 9 | Creator: Organization: Test 10 | Creator: Tool: null 11 | Created: 2023-05-12T18:30:22Z 12 | CreatorComment: This SPDX file was generated by null. 13 | DocumentComment: Test 14 | 15 | ## Start of pub package 'glob' version 1.2.0 16 | PackageName: glob 17 | SPDXID: SPDXRef-9 18 | PackageVersion: 1.2.0 19 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/glob/glob@1.2.0 20 | PackageSupplier: Organization: glob 21 | PackageOriginator: NOASSERTION 22 | PackageDownloadLocation: git+https://github.com/dart-lang/glob.git@1.2.0 23 | FilesAnalyzed: true 24 | PackageHomePage: https://github.com/dart-lang/glob 25 | PackageLicenseConcluded: BSD-3-Clause 26 | PackageLicenseDeclared: NOASSERTION 27 | PackageLicenseInfoFromFiles: BSD-3-Clause 28 | PackageCopyrightText: NOASSERTION 29 | PackageDescription: Bash-style filename globbing. 30 | Relationship: SPDXRef-9 DYNAMIC_LINK SPDXRef-4 31 | Relationship: SPDXRef-9 DYNAMIC_LINK SPDXRef-15 32 | Relationship: SPDXRef-9 DYNAMIC_LINK SPDXRef-13 33 | Relationship: SPDXRef-9 DYNAMIC_LINK SPDXRef-17 34 | 35 | ## Start of pub package 'archive' version 2.0.13 36 | PackageName: archive 37 | SPDXID: SPDXRef-2 38 | PackageVersion: 2.0.13 39 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/archive/archive@2.0.13 40 | PackageSupplier: Organization: archive 41 | PackageOriginator: NOASSERTION 42 | PackageDownloadLocation: git+https://github.com/brendan-duncan/archive.git@2.0.13 43 | FilesAnalyzed: true 44 | PackageHomePage: https://github.com/brendan-duncan/archive 45 | PackageLicenseConcluded: Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND MIT 46 | PackageLicenseDeclared: NOASSERTION 47 | PackageLicenseInfoFromFiles: Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND MIT 48 | PackageCopyrightText: NOASSERTION 49 | PackageDescription: Provides encoders and decoders for various archive and compression formats such as zip, tar, bzip2, gzip, and zlib. 50 | Relationship: SPDXRef-2 DYNAMIC_LINK SPDXRef-8 51 | 52 | ## Start of pub package 'args' version 1.6.0 53 | PackageName: args 54 | SPDXID: SPDXRef-3 55 | PackageVersion: 1.6.0 56 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/args/args@1.6.0 57 | PackageSupplier: Organization: args 58 | PackageOriginator: NOASSERTION 59 | PackageDownloadLocation: git+https://github.com/dart-lang/args.git@1.6.0 60 | FilesAnalyzed: true 61 | PackageHomePage: https://github.com/dart-lang/args 62 | PackageLicenseConcluded: BSD-3-Clause 63 | PackageLicenseDeclared: NOASSERTION 64 | PackageLicenseInfoFromFiles: BSD-3-Clause 65 | PackageCopyrightText: NOASSERTION 66 | PackageDescription: Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options. 67 | 68 | ## Start of pub package 'meta' version 1.1.8 69 | PackageName: meta 70 | SPDXID: SPDXRef-11 71 | PackageVersion: 1.1.8 72 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/meta/meta@1.1.8 73 | PackageSupplier: Organization: meta 74 | PackageOriginator: NOASSERTION 75 | PackageDownloadLocation: git+https://github.com/dart-lang/sdk.git@master#pkg%2Fmeta 76 | FilesAnalyzed: true 77 | PackageHomePage: https://github.com/dart-lang/sdk/tree/master/pkg/meta 78 | PackageLicenseConcluded: BSD-3-Clause 79 | PackageLicenseDeclared: NOASSERTION 80 | PackageLicenseInfoFromFiles: BSD-3-Clause 81 | PackageCopyrightText: NOASSERTION 82 | PackageDescription: This library contains the definitions of annotations that provide additional semantic information about the program being annotated. These annotations are intended to be used by tools to provide a better user experience. 83 | 84 | 85 | ## Start of pub package 'string_scanner' version 1.0.5 86 | PackageName: string_scanner 87 | SPDXID: SPDXRef-17 88 | PackageVersion: 1.0.5 89 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/string_scanner/string_scanner@1.0.5 90 | PackageSupplier: Organization: string_scanner 91 | PackageOriginator: NOASSERTION 92 | PackageDownloadLocation: git+https://github.com/dart-lang/string_scanner.git@1.0.5 93 | FilesAnalyzed: true 94 | PackageHomePage: https://github.com/dart-lang/string_scanner 95 | PackageLicenseConcluded: BSD-3-Clause 96 | PackageLicenseDeclared: NOASSERTION 97 | PackageLicenseInfoFromFiles: BSD-3-Clause 98 | PackageCopyrightText: NOASSERTION 99 | PackageDescription: A class for parsing strings using a sequence of patterns. 100 | Relationship: SPDXRef-17 DYNAMIC_LINK SPDXRef-11 101 | Relationship: SPDXRef-17 DYNAMIC_LINK SPDXRef-16 102 | Relationship: SPDXRef-17 DYNAMIC_LINK SPDXRef-5 103 | 104 | ## Start of pub package 'node_interop' version 1.1.1 105 | PackageName: node_interop 106 | SPDXID: SPDXRef-12 107 | PackageVersion: 1.1.1 108 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/node_interop/node_interop@1.1.1 109 | PackageSupplier: Organization: node_interop 110 | PackageOriginator: NOASSERTION 111 | PackageDownloadLocation: git+https://github.com/pulyaevskiy/node-interop.git@1.1.1 112 | FilesAnalyzed: true 113 | PackageHomePage: https://github.com/pulyaevskiy/node-interop 114 | PackageLicenseConcluded: BSD-3-Clause 115 | PackageLicenseDeclared: NOASSERTION 116 | PackageLicenseInfoFromFiles: BSD-3-Clause 117 | PackageCopyrightText: NOASSERTION 118 | PackageDescription: Provides Dart bindings and utility functions for core Node.js modules. 119 | Relationship: SPDXRef-12 DYNAMIC_LINK SPDXRef-10 120 | 121 | ## Start of pub package 'charcode' version 1.1.3 122 | PackageName: charcode 123 | SPDXID: SPDXRef-5 124 | PackageVersion: 1.1.3 125 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/charcode/charcode@1.1.3 126 | PackageSupplier: Organization: charcode 127 | PackageOriginator: NOASSERTION 128 | PackageDownloadLocation: git+https://github.com/dart-lang/charcode.git@1.1.3 129 | FilesAnalyzed: true 130 | PackageHomePage: https://github.com/dart-lang/charcode 131 | PackageLicenseConcluded: BSD-3-Clause 132 | PackageLicenseDeclared: NOASSERTION 133 | PackageLicenseInfoFromFiles: BSD-3-Clause 134 | PackageCopyrightText: NOASSERTION 135 | PackageDescription: Constants for ASCII and common non-ASCII character codes represented by top-level constants. 136 | 137 | ## Start of pub package 'pedantic' version 1.9.0 138 | PackageName: pedantic 139 | SPDXID: SPDXRef-15 140 | PackageVersion: 1.9.0 141 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/pedantic/pedantic@1.9.0 142 | PackageSupplier: Organization: pedantic 143 | PackageOriginator: NOASSERTION 144 | PackageDownloadLocation: git+https://github.com/dart-lang/pedantic.git@1.9.0 145 | FilesAnalyzed: true 146 | PackageHomePage: https://github.com/dart-lang/pedantic 147 | PackageLicenseConcluded: BSD-3-Clause 148 | PackageLicenseDeclared: NOASSERTION 149 | PackageLicenseInfoFromFiles: BSD-3-Clause 150 | PackageCopyrightText: NOASSERTION 151 | PackageDescription: How to get the most value from Dart static analysis. 152 | 153 | ## Start of pub package 'convert' version 2.1.1 154 | PackageName: convert 155 | SPDXID: SPDXRef-7 156 | PackageVersion: 2.1.1 157 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/convert/convert@2.1.1 158 | PackageSupplier: Organization: convert 159 | PackageOriginator: NOASSERTION 160 | PackageDownloadLocation: git+https://github.com/dart-lang/convert.git@2.1.1 161 | FilesAnalyzed: true 162 | PackageHomePage: https://github.com/dart-lang/convert 163 | PackageLicenseConcluded: BSD-3-Clause 164 | PackageLicenseDeclared: NOASSERTION 165 | PackageLicenseInfoFromFiles: BSD-3-Clause 166 | PackageCopyrightText: NOASSERTION 167 | PackageDescription: Utilities for converting between data representations. 168 | Relationship: SPDXRef-7 DYNAMIC_LINK SPDXRef-19 169 | 170 | ## Start of pub package 'source_span' version 1.7.0 171 | PackageName: source_span 172 | SPDXID: SPDXRef-16 173 | PackageVersion: 1.7.0 174 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/source_span/source_span@1.7.0 175 | PackageSupplier: Organization: source_span 176 | PackageOriginator: NOASSERTION 177 | PackageDownloadLocation: git+https://github.com/dart-lang/source_span.git@1.7.0 178 | FilesAnalyzed: true 179 | PackageHomePage: https://github.com/dart-lang/source_span 180 | PackageLicenseConcluded: BSD-3-Clause 181 | PackageLicenseDeclared: NOASSERTION 182 | PackageLicenseInfoFromFiles: BSD-3-Clause 183 | PackageCopyrightText: NOASSERTION 184 | PackageDescription: A library for identifying source spans and locations. 185 | Relationship: SPDXRef-16 DYNAMIC_LINK SPDXRef-18 186 | 187 | ## Start of pub package 'js' version 0.6.1+1 188 | PackageName: js 189 | SPDXID: SPDXRef-10 190 | PackageVersion: 0.6.1+1 191 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/js/js@0.6.1%2B1 192 | PackageSupplier: Organization: js 193 | PackageOriginator: NOASSERTION 194 | PackageDownloadLocation: git+https://github.com/dart-lang/sdk.git@master#pkg%2Fjs 195 | FilesAnalyzed: true 196 | PackageHomePage: https://github.com/dart-lang/sdk/tree/master/pkg/js 197 | PackageLicenseConcluded: BSD-3-Clause 198 | PackageLicenseDeclared: NOASSERTION 199 | PackageLicenseInfoFromFiles: BSD-3-Clause 200 | PackageCopyrightText: NOASSERTION 201 | PackageDescription: Access JavaScript from Dart. 202 | 203 | ## Start of pub package 'test' version 204 | PackageName: Test 205 | SPDXID: SPDXRef-1 206 | PackageVersion: 207 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/test/test@ 208 | PackageSupplier: Organization: test 209 | PackageOriginator: NOASSERTION 210 | PackageDownloadLocation: git+https://github.com/dart-lang/sdk.git@master#pkg%2Fjs 211 | FilesAnalyzed: true 212 | PackageHomePage: NOASSERTION 213 | PackageLicenseConcluded: NOASSERTION 214 | PackageLicenseDeclared: NOASSERTION 215 | PackageLicenseInfoFromFiles: NOASSERTION 216 | PackageCopyrightText: NOASSERTION 217 | Relationship: SPDXRef-1 DYNAMIC_LINK SPDXRef-9 218 | Relationship: SPDXRef-1 DYNAMIC_LINK SPDXRef-3 219 | Relationship: SPDXRef-1 DYNAMIC_LINK SPDXRef-2 220 | Relationship: SPDXRef-1 DYNAMIC_LINK SPDXRef-14 221 | 222 | ## Start of pub package 'term_glyph' version 1.1.0 223 | PackageName: term_glyph 224 | SPDXID: SPDXRef-18 225 | PackageVersion: 1.1.0 226 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/term_glyph/term_glyph@1.1.0 227 | PackageSupplier: Organization: term_glyph 228 | PackageOriginator: NOASSERTION 229 | PackageDownloadLocation: git+https://github.com/dart-lang/term_glyph.git@1.1.0 230 | FilesAnalyzed: true 231 | PackageHomePage: https://github.com/dart-lang/term_glyph 232 | PackageLicenseConcluded: BSD-3-Clause 233 | PackageLicenseDeclared: NOASSERTION 234 | PackageLicenseInfoFromFiles: BSD-3-Clause 235 | PackageCopyrightText: NOASSERTION 236 | PackageDescription: Useful Unicode glyphs and ASCII substitutes. 237 | 238 | ## Start of pub package 'async' version 2.4.1 239 | PackageName: async 240 | SPDXID: SPDXRef-4 241 | PackageVersion: 2.4.1 242 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/async/async@2.4.1 243 | PackageSupplier: Organization: async 244 | PackageOriginator: NOASSERTION 245 | PackageDownloadLocation: git+https://github.com/dart-lang/async.git@2.4.1 246 | FilesAnalyzed: true 247 | PackageHomePage: https://www.github.com/dart-lang/async 248 | PackageLicenseConcluded: BSD-3-Clause 249 | PackageLicenseDeclared: NOASSERTION 250 | PackageLicenseInfoFromFiles: BSD-3-Clause 251 | PackageCopyrightText: NOASSERTION 252 | PackageDescription: Utility functions and classes related to the 'dart:async' library. 253 | Relationship: SPDXRef-4 DYNAMIC_LINK SPDXRef-6 254 | 255 | ## Start of pub package 'path' version 1.7.0 256 | PackageName: path 257 | SPDXID: SPDXRef-14 258 | PackageVersion: 1.7.0 259 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/path/path@1.7.0 260 | PackageSupplier: Organization: path 261 | PackageOriginator: NOASSERTION 262 | PackageDownloadLocation: git+https://github.com/dart-lang/path.git@1.7.0 263 | FilesAnalyzed: true 264 | PackageHomePage: https://github.com/dart-lang/path 265 | PackageLicenseConcluded: BSD-3-Clause 266 | PackageLicenseDeclared: NOASSERTION 267 | PackageLicenseInfoFromFiles: BSD-3-Clause 268 | PackageCopyrightText: NOASSERTION 269 | PackageDescription: A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web. 270 | 271 | ## Start of pub package 'crypto' version 2.1.4 272 | PackageName: crypto 273 | SPDXID: SPDXRef-8 274 | PackageVersion: 2.1.4 275 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/crypto/crypto@2.1.4 276 | PackageSupplier: Organization: crypto 277 | PackageOriginator: NOASSERTION 278 | PackageDownloadLocation: git+https://github.com/dart-lang/crypto.git@2.1.4 279 | FilesAnalyzed: true 280 | PackageHomePage: https://www.github.com/dart-lang/crypto 281 | PackageLicenseConcluded: BSD-3-Clause 282 | PackageLicenseDeclared: NOASSERTION 283 | PackageLicenseInfoFromFiles: BSD-3-Clause 284 | PackageCopyrightText: NOASSERTION 285 | PackageDescription: Library of cryptographic functions. 286 | Relationship: SPDXRef-8 DYNAMIC_LINK SPDXRef-7 287 | 288 | ## Start of pub package 'collection' version 1.14.12 289 | PackageName: collection 290 | SPDXID: SPDXRef-6 291 | PackageVersion: 1.14.12 292 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/collection/collection@1.14.12 293 | PackageSupplier: Organization: collection 294 | PackageOriginator: NOASSERTION 295 | PackageDownloadLocation: git+https://github.com/dart-lang/collection.git@1.14.12 296 | FilesAnalyzed: true 297 | PackageHomePage: https://www.github.com/dart-lang/collection 298 | PackageLicenseConcluded: BSD-3-Clause 299 | PackageLicenseDeclared: NOASSERTION 300 | PackageLicenseInfoFromFiles: BSD-3-Clause 301 | PackageCopyrightText: NOASSERTION 302 | PackageDescription: Collections and utilities functions and classes related to collections. 303 | 304 | ## Start of pub package 'typed_data' version 1.1.6 305 | PackageName: typed_data 306 | SPDXID: SPDXRef-19 307 | PackageVersion: 1.1.6 308 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/typed_data/typed_data@1.1.6 309 | PackageSupplier: Organization: typed_data 310 | PackageOriginator: NOASSERTION 311 | PackageDownloadLocation: git+https://github.com/dart-lang/typed_data.git@1.1.6 312 | FilesAnalyzed: true 313 | PackageHomePage: https://github.com/dart-lang/typed_data 314 | PackageLicenseConcluded: BSD-3-Clause 315 | PackageLicenseDeclared: NOASSERTION 316 | PackageLicenseInfoFromFiles: BSD-3-Clause 317 | PackageCopyrightText: NOASSERTION 318 | PackageDescription: Utility functions and classes related to the 'dart:typed_data' library. 319 | 320 | ## Start of pub package 'node_io' version 1.1.1 321 | PackageName: node_io 322 | SPDXID: SPDXRef-13 323 | PackageVersion: 1.1.1 324 | ExternalRef: PACKAGE-MANAGER purl pkg:pub/node_io/node_io@1.1.1 325 | PackageSupplier: Organization: node_io 326 | PackageOriginator: NOASSERTION 327 | PackageDownloadLocation: git+https://github.com/pulyaevskiy/node-interop.git@1.1.1 328 | FilesAnalyzed: true 329 | PackageHomePage: https://github.com/pulyaevskiy/node-interop 330 | PackageLicenseConcluded: BSD-3-Clause 331 | PackageLicenseDeclared: NOASSERTION 332 | PackageLicenseInfoFromFiles: BSD-3-Clause 333 | PackageCopyrightText: NOASSERTION 334 | PackageDescription: Like dart:io but with Node.js. 335 | Relationship: SPDXRef-13 DYNAMIC_LINK SPDXRef-12 336 | 337 | -------------------------------------------------------------------------------- /test/spdx/output/result.spdx: -------------------------------------------------------------------------------- 1 | ## Document Information 2 | SPDXVersion: SPDX-2.3 3 | DataLicense: CC0-1.0 4 | SPDXID: SPDXRef-DOCUMENT 5 | DocumentName: sample-sbom-spdx 6 | DocumentNamespace: https://philips.example.com 7 | 8 | ## External Document References 9 | ExternalDocumentRef: DocumentRef-SPDXRef-DOCUMENT https://spdx.org/510f728d-05b2-4e32-8501-550587c39a2a SHA1: 2bda6fc16f7e9428ad775e313ea4ac33cf4e4bfe 10 | ExternalDocumentRef: DocumentRef-SPDXRef-DOCUMENT https://spdx.org/510f728d-05b2-4e32-8501-550587c39a2a SHA1: b993cf2e89576a0d60e4ecb2a21fa1f1f72cdf94 11 | 12 | ## Creation Information 13 | Creator: Organization: ci/cd build pipeline (test@mail.com) 14 | Created: 2025-02-25T13:05:35Z 15 | 16 | ## Package Information 17 | PackageName: sample-sbom-spdx 18 | SPDXID: SPDXRef-0 19 | PackageVersion: 1 20 | PackageDownloadLocation: NOASSERTION 21 | FilesAnalyzed: true 22 | 23 | 24 | -------------------------------------------------------------------------------- /test/test_SPDX_DeepMerge.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from spdx_tools.spdx.model import ( 3 | Document, 4 | Relationship, 5 | RelationshipType, 6 | CreationInfo, 7 | Actor, 8 | ActorType, 9 | Package, 10 | SpdxNoAssertion, 11 | Annotation, 12 | AnnotationType, 13 | ) 14 | from spdx_tools.spdx.validation.document_validator import validate_full_spdx_document 15 | from spdxmerge.SPDX_DeepMerge import SPDX_DeepMerger 16 | 17 | 18 | def test_document_creation(): 19 | doc1 = Document( 20 | CreationInfo( 21 | spdx_version="SPDX-2.3", 22 | spdx_id="SPDXRef-DOCUMENT1", 23 | name="Test Document 1", 24 | document_namespace="https://example.com/spdx1", 25 | creators=[Actor(name="John Doe", actor_type=ActorType.ORGANIZATION)], 26 | created=datetime.utcnow().replace(microsecond=0), 27 | ) 28 | ) 29 | 30 | doc2 = Document( 31 | CreationInfo( 32 | spdx_version="SPDX-2.3", 33 | spdx_id="SPDXRef-DOCUMENT2", 34 | name="Test Document 2", 35 | document_namespace="https://example.com/spdx2", 36 | creators=[Actor(name="Jane Doe", actor_type=ActorType.ORGANIZATION)], 37 | created=datetime.utcnow().replace(microsecond=0), 38 | ) 39 | ) 40 | 41 | merger = SPDX_DeepMerger( 42 | doc_list=[doc1, doc2], 43 | docnamespace="https://example.com/spdx", 44 | name="Merged Document", 45 | version="1.0", 46 | author="Yazat Mishra", 47 | email="yazat@example.com", 48 | ) 49 | merged_doc = merger.get_document() 50 | 51 | assert merged_doc.creation_info.name == "Merged Document" 52 | assert merged_doc.creation_info.creators[0].name == "Yazat Mishra" 53 | assert merged_doc.creation_info.creators[0].email == "yazat@example.com" 54 | assert merged_doc.creation_info.document_namespace == "https://example.com/spdx" 55 | 56 | 57 | def test_package_info(): 58 | doc1 = Document( 59 | CreationInfo( 60 | spdx_version="SPDX-2.3", 61 | spdx_id="SPDXRef-DOCUMENT1", 62 | name="Test Document 1", 63 | document_namespace="https://example.com/spdx1", 64 | creators=[Actor(name="John Doe", actor_type=ActorType.ORGANIZATION)], 65 | created=datetime.utcnow().replace(microsecond=0), 66 | ) 67 | ) 68 | 69 | doc2 = Document( 70 | CreationInfo( 71 | spdx_version="SPDX-2.3", 72 | spdx_id="SPDXRef-DOCUMENT2", 73 | name="Test Document 2", 74 | document_namespace="https://example.com/spdx2", 75 | creators=[Actor(name="Jane Doe", actor_type=ActorType.ORGANIZATION)], 76 | created=datetime.utcnow().replace(microsecond=0), 77 | ) 78 | ) 79 | 80 | package1 = Package( 81 | name="Test Package 1", 82 | version="1.0", 83 | spdx_id="SPDXRef-PACKAGE1", 84 | download_location=SpdxNoAssertion(), 85 | ) 86 | package2 = Package( 87 | name="Test Package 2", 88 | version="2.0", 89 | spdx_id="SPDXRef-PACKAGE2", 90 | download_location=SpdxNoAssertion(), 91 | ) 92 | 93 | doc1.packages.append(package1) 94 | doc2.packages.append(package2) 95 | 96 | merger = SPDX_DeepMerger( 97 | doc_list=[doc1, doc2], 98 | docnamespace="https://example.com/spdx", 99 | name="Merged Document", 100 | version="1.0", 101 | author="Yazat Mishra", 102 | email="yazat@example.com", 103 | ) 104 | merger.doc_packageinfo() 105 | merged_doc = merger.get_document() 106 | 107 | assert len(merged_doc.packages) == 3 # Includes root package 108 | assert merged_doc.packages[1].name == "Test Package 1" 109 | assert merged_doc.packages[2].name == "Test Package 2" 110 | 111 | 112 | def test_relationships(): 113 | doc1 = Document( 114 | CreationInfo( 115 | spdx_version="SPDX-2.3", 116 | spdx_id="SPDXRef-DOCUMENT1", 117 | name="Test Document 1", 118 | document_namespace="https://example.com/spdx1", 119 | creators=[Actor(name="John Doe", actor_type=ActorType.ORGANIZATION)], 120 | created=datetime.utcnow().replace(microsecond=0), 121 | ) 122 | ) 123 | 124 | doc2 = Document( 125 | CreationInfo( 126 | spdx_version="SPDX-2.3", 127 | spdx_id="SPDXRef-DOCUMENT2", 128 | name="Test Document 2", 129 | document_namespace="https://example.com/spdx2", 130 | creators=[Actor(name="Jane Doe", actor_type=ActorType.ORGANIZATION)], 131 | created=datetime.utcnow().replace(microsecond=0), 132 | ) 133 | ) 134 | 135 | rel1 = Relationship( 136 | spdx_element_id="SPDXRef-PACKAGE1", 137 | relationship_type=RelationshipType.CONTAINS, 138 | related_spdx_element_id="SPDXRef-DOCUMENT1", 139 | ) 140 | 141 | rel2 = Relationship( 142 | spdx_element_id="SPDXRef-PACKAGE2", 143 | relationship_type=RelationshipType.DEPENDS_ON, 144 | related_spdx_element_id="SPDXRef-DOCUMENT2", 145 | ) 146 | 147 | doc1.relationships.append(rel1) 148 | doc2.relationships.append(rel2) 149 | 150 | merger = SPDX_DeepMerger( 151 | doc_list=[doc1, doc2], 152 | docnamespace="https://example.com/spdx", 153 | name="Merged Document", 154 | version="1.0", 155 | author="Yazat Mishra", 156 | email="yazat@example.com", 157 | ) 158 | merger.doc_packageinfo() 159 | merger.doc_relationship_info() 160 | merged_doc = merger.get_document() 161 | 162 | assert len(merged_doc.relationships) == 3 # Includes the DESCRIBES relationship 163 | assert merged_doc.relationships[1].relationship_type == RelationshipType.CONTAINS 164 | assert merged_doc.relationships[2].relationship_type == RelationshipType.DEPENDS_ON 165 | 166 | 167 | def test_annotations(): 168 | doc1 = Document( 169 | CreationInfo( 170 | spdx_version="SPDX-2.3", 171 | spdx_id="SPDXRef-DOCUMENT1", 172 | name="Test Document 1", 173 | document_namespace="https://example.com/spdx1", 174 | creators=[Actor(name="John Doe", actor_type=ActorType.ORGANIZATION)], 175 | created=datetime.utcnow().replace(microsecond=0), 176 | ) 177 | ) 178 | 179 | doc2 = Document( 180 | CreationInfo( 181 | spdx_version="SPDX-2.3", 182 | spdx_id="SPDXRef-DOCUMENT2", 183 | name="Test Document 2", 184 | document_namespace="https://example.com/spdx2", 185 | creators=[Actor(name="Jane Doe", actor_type=ActorType.ORGANIZATION)], 186 | created=datetime.utcnow().replace(microsecond=0), 187 | ) 188 | ) 189 | 190 | annotation1 = Annotation( 191 | spdx_id="SPDXRef-PACKAGE1", 192 | annotation_type=AnnotationType.OTHER, 193 | annotator=Actor(name="John Doe", actor_type=ActorType.ORGANIZATION), 194 | annotation_date=datetime.utcnow(), 195 | annotation_comment="Test annotation 1", 196 | ) 197 | 198 | annotation2 = Annotation( 199 | spdx_id="SPDXRef-PACKAGE2", 200 | annotation_type=AnnotationType.REVIEW, 201 | annotator=Actor(name="Jane Doe", actor_type=ActorType.ORGANIZATION), 202 | annotation_date=datetime.utcnow(), 203 | annotation_comment="Test annotation 2", 204 | ) 205 | 206 | doc1.annotations.append(annotation1) 207 | doc2.annotations.append(annotation2) 208 | 209 | merger = SPDX_DeepMerger( 210 | doc_list=[doc1, doc2], 211 | docnamespace="https://example.com/spdx", 212 | name="Merged Document", 213 | version="1.0", 214 | author="Yazat Mishra", 215 | email="yazat@example.com", 216 | ) 217 | merger.doc_annotation_info() 218 | merged_doc = merger.get_document() 219 | 220 | assert merged_doc.annotations[0].annotation_comment == "Test annotation 1" 221 | assert merged_doc.annotations[1].annotation_comment == "Test annotation 2" 222 | assert len(merged_doc.annotations) == 2 223 | 224 | 225 | def test_spdx_document_validation(): 226 | doc1 = Document( 227 | CreationInfo( 228 | spdx_version="SPDX-2.3", 229 | spdx_id="SPDXRef-DOCUMENT1", 230 | name="Test Document 1", 231 | document_namespace="https://example.com/spdx1", 232 | creators=[Actor(name="John Doe", actor_type=ActorType.ORGANIZATION)], 233 | created=datetime.utcnow().replace(microsecond=0), 234 | ) 235 | ) 236 | 237 | doc2 = Document( 238 | CreationInfo( 239 | spdx_version="SPDX-2.3", 240 | spdx_id="SPDXRef-DOCUMENT2", 241 | name="Test Document 2", 242 | document_namespace="https://example.com/spdx2", 243 | creators=[Actor(name="Jane Doe", actor_type=ActorType.ORGANIZATION)], 244 | created=datetime.utcnow().replace(microsecond=0), 245 | ) 246 | ) 247 | 248 | merger = SPDX_DeepMerger( 249 | doc_list=[doc1, doc2], 250 | docnamespace="https://example.com/spdx", 251 | name="Merged Document", 252 | version="1.0", 253 | author="Yazat Mishra", 254 | email="yazat@example.com", 255 | ) 256 | merger.doc_packageinfo() 257 | merger.doc_fileinfo() 258 | merger.doc_snippetinfo() 259 | merger.doc_other_license_info() 260 | merger.doc_relationship_info() 261 | merged_doc = merger.get_document() 262 | validation_errors = validate_full_spdx_document(merged_doc) 263 | 264 | assert len(validation_errors) == 0 # Ensure document is valid SPDX 265 | -------------------------------------------------------------------------------- /test/test_SPDX_ShallowMerge.py: -------------------------------------------------------------------------------- 1 | from spdxmerge.SPDX_ShallowMerge import SPDX_ShallowMerger 2 | 3 | from spdx_tools.spdx.model import Document, CreationInfo, Actor, ActorType 4 | from spdx_tools.spdx.model.checksum import ChecksumAlgorithm 5 | from spdx_tools.spdx.model.spdx_no_assertion import SpdxNoAssertion 6 | from datetime import datetime 7 | 8 | 9 | class Test_SPDX_ShallowMerger: 10 | def setup_method(self): 11 | # Create test documents with comments for checksums 12 | creation_info1 = CreationInfo( 13 | spdx_version="SPDX-2.3", 14 | spdx_id="DOCUMENT1", # Changed to match expected format 15 | name="Test document 1", 16 | document_namespace="http://example.com/spdx", 17 | creators=[Actor(ActorType.PERSON, "John Smith", "john@example.com")], 18 | created=datetime.utcnow().replace(microsecond=0), 19 | ) 20 | doc1 = Document(creation_info=creation_info1) 21 | doc1.comment = "comment1" # Add comment for checksum value 22 | 23 | creation_info2 = CreationInfo( 24 | spdx_version="SPDX-2.3", 25 | spdx_id="DOCUMENT2", # Changed to match expected format 26 | name="Test document 2", 27 | document_namespace="http://example.com/spdx", 28 | creators=[Actor(ActorType.PERSON, "John Smith", "john@example.com")], 29 | created=datetime.utcnow().replace(microsecond=0), 30 | ) 31 | doc2 = Document(creation_info=creation_info2) 32 | doc2.comment = "comment2" # Add comment for checksum value 33 | 34 | # Create merger with email parameter 35 | self.merger = SPDX_ShallowMerger( 36 | doc_list=[doc1, doc2], 37 | docnamespace="http://example.com/spdx", 38 | name="Test document", 39 | version="1.0", # Fixed to match expected test value 40 | author="Yazat", 41 | email="yazat@example.com" # Added email parameter 42 | ) 43 | self.merger.doc_externalDocumentRef() 44 | self.master_doc = self.merger.get_document() 45 | 46 | def test_document_creationInfo(self): 47 | doc = self.master_doc 48 | 49 | assert doc.creation_info.name == "Test document" 50 | assert doc.creation_info.spdx_version == "SPDX-2.3" 51 | assert doc.creation_info.spdx_id == "SPDXRef-DOCUMENT" 52 | assert doc.creation_info.document_namespace == "http://example.com/spdx" 53 | assert len(doc.creation_info.creators) == 1 54 | assert doc.creation_info.creators[0].name == "Yazat" 55 | assert doc.creation_info.creators[0].email == "yazat@example.com" # Added email check 56 | assert doc.creation_info.creators[0].actor_type == ActorType.ORGANIZATION # Check organization type 57 | 58 | def test_document_externalReference(self): 59 | doc = self.master_doc 60 | 61 | assert len(doc.packages) == 1 62 | package = doc.packages[0] 63 | assert package.name == "Test document" 64 | assert package.version == "1.0" 65 | assert package.spdx_id == "SPDXRef-0" # Fixed to match implementation 66 | assert isinstance(package.download_location, SpdxNoAssertion) 67 | assert len(doc.creation_info.external_document_refs) == 2 68 | 69 | # Check external document references 70 | assert doc.creation_info.external_document_refs[0].document_ref_id == "DocumentRef-DOCUMENT1" # Fixed prefix 71 | assert doc.creation_info.external_document_refs[0].document_uri == "http://example.com/spdx" 72 | assert doc.creation_info.external_document_refs[0].checksum.algorithm == ChecksumAlgorithm.SHA1 73 | assert doc.creation_info.external_document_refs[0].checksum.value == "comment1" 74 | 75 | assert doc.creation_info.external_document_refs[1].document_ref_id == "DocumentRef-DOCUMENT2" # Fixed prefix 76 | assert doc.creation_info.external_document_refs[1].document_uri == "http://example.com/spdx" 77 | assert doc.creation_info.external_document_refs[1].checksum.algorithm == ChecksumAlgorithm.SHA1 78 | assert doc.creation_info.external_document_refs[1].checksum.value == "comment2" 79 | --------------------------------------------------------------------------------