zephyr/scripts/west_commands/zspdx/cmakefileapi.py
Steve Winslow fd31b9b4ac west: spdx: Generate SPDX 2.2 tag-value documents
This adds support to generate SPDX 2.2 tag-value documents via the
new west spdx command. The CMake file-based APIs are leveraged to
create relationships from source files to the corresponding
generated build files. SPDX-License-Identifier comments in source
files are scanned and filled into the SPDX documents.

Before `west build` is run, a specific file must be created in the
build directory so that the CMake API reply will run. This can be
done by running:

    west spdx --init -d BUILD_DIR

After `west build` is run, SPDX generation is then activated by
calling `west spdx`; currently this requires passing the build
directory as a parameter again:

    west spdx -d BUILD_DIR

This will generate three SPDX documents in `BUILD_DIR/spdx/`:

1) `app.spdx`: This contains the bill-of-materials for the
application source files used for the build.

2) `zephyr.spdx`: This contains the bill-of-materials for the
specific Zephyr source code files that are used for the build.

3) `build.spdx`: This contains the bill-of-materials for the built
output files.

Each file in the bill-of-materials is scanned, so that its hashes
(SHA256 and SHA1) can be recorded, along with any detected licenses
if an `SPDX-License-Identifier` appears in the file.

SPDX Relationships are created to indicate dependencies between
CMake build targets; build targets that are linked together; and
source files that are compiled to generate the built library files.

`west spdx` can be called with optional parameters for further
configuration:

* `-n PREFIX`: specifies a prefix for the Document Namespaces that
will be included in the generated SPDX documents. See SPDX spec 2.2
section 2.5 at
https://spdx.github.io/spdx-spec/2-document-creation-information/.
If -n is omitted, a default namespace will be generated according
to the default format described in section 2.5 using a random UUID.

* `-s SPDX_DIR`: specifies an alternate directory where the SPDX
documents should be written. If not specified, they will be saved
in `BUILD_DIR/spdx/`.

* `--analyze-includes`: in addition to recording the compiled
source code files (e.g. `.c`, `.S`) in the bills-of-materials, if
this flag is specified, `west spdx` will attempt to determine the
specific header files that are included for each `.c` file. This
will take longer, as it performs a dry run using the C compiler
for each `.c` file (using the same arguments that were passed to it
for the actual build).

* `--include-sdk`: if `--analyze-includes` is used, then adding
`--include-sdk` will create a fourth SPDX document, `sdk.spdx`,
which will list any header files included from the SDK.

Signed-off-by: Steve Winslow <steve@swinslow.net>
2021-05-05 11:14:06 -04:00

307 lines
7.7 KiB
Python

# Copyright (c) 2020 The Linux Foundation
#
# SPDX-License-Identifier: Apache-2.0
from enum import Enum
class Codemodel:
def __init__(self):
super(Codemodel, self).__init__()
self.paths_source = ""
self.paths_build = ""
self.configurations = []
def __repr__(self):
return f"Codemodel: source {self.paths_source}, build {self.paths_build}"
# A member of the codemodel configurations array
class Config:
def __init__(self):
super(Config, self).__init__()
self.name = ""
self.directories = []
self.projects = []
self.configTargets = []
def __repr__(self):
if self.name == "":
return f"Config: [no name]"
else:
return f"Config: {self.name}"
# A member of the configuration.directories array
class ConfigDir:
def __init__(self):
super(ConfigDir, self).__init__()
self.source = ""
self.build = ""
self.parentIndex = -1
self.childIndexes = []
self.projectIndex = -1
self.targetIndexes = []
self.minimumCMakeVersion = ""
self.hasInstallRule = False
# actual items, calculated from indices after loading
self.parent = None
self.children = []
self.project = None
self.targets = []
def __repr__(self):
return f"ConfigDir: source {self.source}, build {self.build}"
# A member of the configuration.projects array
class ConfigProject:
def __init__(self):
super(ConfigProject, self).__init__()
self.name = ""
self.parentIndex = -1
self.childIndexes = []
self.directoryIndexes = []
self.targetIndexes = []
# actual items, calculated from indices after loading
self.parent = None
self.children = []
self.directories = []
self.targets = []
def __repr__(self):
return f"ConfigProject: {self.name}"
# A member of the configuration.configTargets array
class ConfigTarget:
def __init__(self):
super(ConfigTarget, self).__init__()
self.name = ""
self.id = ""
self.directoryIndex = -1
self.projectIndex = -1
self.jsonFile = ""
# actual target data, loaded from self.jsonFile
self.target = None
# actual items, calculated from indices after loading
self.directory = None
self.project = None
def __repr__(self):
return f"ConfigTarget: {self.name}"
# The available values for Target.type
class TargetType(Enum):
UNKNOWN = 0
EXECUTABLE = 1
STATIC_LIBRARY = 2
SHARED_LIBRARY = 3
MODULE_LIBRARY = 4
OBJECT_LIBRARY = 5
UTILITY = 6
# A member of the target.install_destinations array
class TargetInstallDestination:
def __init__(self):
super(TargetInstallDestination, self).__init__()
self.path = ""
self.backtrace = -1
def __repr__(self):
return f"TargetInstallDestination: {self.path}"
# A member of the target.link_commandFragments and
# archive_commandFragments array
class TargetCommandFragment:
def __init__(self):
super(TargetCommandFragment, self).__init__()
self.fragment = ""
self.role = ""
def __repr__(self):
return f"TargetCommandFragment: {self.fragment}"
# A member of the target.dependencies array
class TargetDependency:
def __init__(self):
super(TargetDependency, self).__init__()
self.id = ""
self.backtrace = -1
def __repr__(self):
return f"TargetDependency: {self.id}"
# A member of the target.sources array
class TargetSource:
def __init__(self):
super(TargetSource, self).__init__()
self.path = ""
self.compileGroupIndex = -1
self.sourceGroupIndex = -1
self.isGenerated = False
self.backtrace = -1
# actual items, calculated from indices after loading
self.compileGroup = None
self.sourceGroup = None
def __repr__(self):
return f"TargetSource: {self.path}"
# A member of the target.sourceGroups array
class TargetSourceGroup:
def __init__(self):
super(TargetSourceGroup, self).__init__()
self.name = ""
self.sourceIndexes = []
# actual items, calculated from indices after loading
self.sources = []
def __repr__(self):
return f"TargetSourceGroup: {self.name}"
# A member of the target.compileGroups.includes array
class TargetCompileGroupInclude:
def __init__(self):
super(TargetCompileGroupInclude, self).__init__()
self.path = ""
self.isSystem = False
self.backtrace = -1
def __repr__(self):
return f"TargetCompileGroupInclude: {self.path}"
# A member of the target.compileGroups.precompileHeaders array
class TargetCompileGroupPrecompileHeader:
def __init__(self):
super(TargetCompileGroupPrecompileHeader, self).__init__()
self.header = ""
self.backtrace = -1
def __repr__(self):
return f"TargetCompileGroupPrecompileHeader: {self.header}"
# A member of the target.compileGroups.defines array
class TargetCompileGroupDefine:
def __init__(self):
super(TargetCompileGroupDefine, self).__init__()
self.define = ""
self.backtrace = -1
def __repr__(self):
return f"TargetCompileGroupDefine: {self.define}"
# A member of the target.compileGroups array
class TargetCompileGroup:
def __init__(self):
super(TargetCompileGroup, self).__init__()
self.sourceIndexes = []
self.language = ""
self.compileCommandFragments = []
self.includes = []
self.precompileHeaders = []
self.defines = []
self.sysroot = ""
# actual items, calculated from indices after loading
self.sources = []
def __repr__(self):
return f"TargetCompileGroup: {self.sources}"
# A member of the target.backtraceGraph_nodes array
class TargetBacktraceGraphNode:
def __init__(self):
super(TargetBacktraceGraphNode, self).__init__()
self.file = -1
self.line = -1
self.command = -1
self.parent = -1
def __repr__(self):
return f"TargetBacktraceGraphNode: {self.command}"
# Actual data in config.target.target, loaded from
# config.target.jsonFile
class Target:
def __init__(self):
super(Target, self).__init__()
self.name = ""
self.id = ""
self.type = TargetType.UNKNOWN
self.backtrace = -1
self.folder = ""
self.paths_source = ""
self.paths_build = ""
self.nameOnDisk = ""
self.artifacts = []
self.isGeneratorProvided = False
# only if install rule is present
self.install_prefix = ""
self.install_destinations = []
# only for executables and shared library targets that link into
# a runtime binary
self.link_language = ""
self.link_commandFragments = []
self.link_lto = False
self.link_sysroot = ""
# only for static library targets
self.archive_commandFragments = []
self.archive_lto = False
# only if the target depends on other targets
self.dependencies = []
# corresponds to target's source files
self.sources = []
# only if sources are grouped together by source_group() or by default
self.sourceGroups = []
# only if target has sources that compile
self.compileGroups = []
# graph of backtraces referenced from elsewhere
self.backtraceGraph_nodes = []
self.backtraceGraph_commands = []
self.backtraceGraph_files = []
def __repr__(self):
return f"Target: {self.name}"