Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/packagedcode/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from packagedcode import freebsd
from packagedcode import godeps
from packagedcode import golang
from packagedcode import gradle
from packagedcode import haxe
from packagedcode import maven
from packagedcode import misc
Expand All @@ -40,6 +41,7 @@
from packagedcode import swift
from packagedcode import win_pe
from packagedcode import windows
from packagedcode.gradle import GradleModuleHandler

if on_linux:
from packagedcode import msi
Expand All @@ -56,6 +58,7 @@
bower.BowerJsonHandler,

build_gradle.BuildGradleHandler,
gradle.GradleModuleHandler,

build.AutotoolsConfigureHandler,
build.BazelBuildHandler,
Expand Down
118 changes: 118 additions & 0 deletions src/packagedcode/gradle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#
# Copyright (c) nexB Inc. and others. All rights reserved.
# ScanCode is a trademark of nexB Inc.
# SPDX-License-Identifier: Apache-2.0
# See http://www.apache.org/licenses/LICENSE-2.0 for the license text.
# See https://github.com/nexB/scancode-toolkit for support or download.
# See https://aboutcode.org for more information about nexB OSS projects.
#

import json

from packageurl import PackageURL

from packagedcode import models


class GradleModuleHandler(models.DatafileHandler):
datasource_id = 'gradle_module'
path_patterns = ('*.module',)
default_package_type = 'maven'
description = 'Gradle module metadata file'

@classmethod
def is_datafile(cls, location, filetypes=tuple()):
if super().is_datafile(location, filetypes=filetypes):
return True
return str(location).endswith('.module')

@classmethod
def parse(cls, location, package_only=False):
try:
with open(location, 'r') as f:
data = json.load(f)
except Exception:
return

if not data or not isinstance(data, dict):
return

component = data.get('component', {})
if not component:
return

namespace = component.get('group', '')
name = component.get('module', '')
version = component.get('version', '')

created_by = data.get('createdBy', {})
gradle_version = created_by.get('gradle', {}).get('version')

variants = data.get('variants', [])
seen_deps = {}
files = []

for variant in variants:
variant_name = variant.get('name', '')
attributes = variant.get('attributes', {})
usage = attributes.get('org.gradle.usage', variant_name)

is_runtime = 'runtime' in usage.lower()
scope = 'runtime' if is_runtime else 'api'

for dep in variant.get('dependencies', []):
dep_namespace = dep.get('group', '')
dep_name = dep.get('module', '')
dep_version_info = dep.get('version', {})

if isinstance(dep_version_info, dict):
dep_version = dep_version_info.get('requires') or \
dep_version_info.get('prefers') or \
dep_version_info.get('strictly', '')
else:
dep_version = str(dep_version_info) if dep_version_info else ''

key = (dep_namespace, dep_name, dep_version)
if key not in seen_deps:
seen_deps[key] = models.DependentPackage(
purl=str(PackageURL(
type='maven',
namespace=dep_namespace,
name=dep_name,
version=dep_version
)),
extracted_requirement=dep_version,
scope=scope,
is_runtime=is_runtime,
is_optional=False,
)

# Extract files from the first variant that has them
if not files and variant.get('files'):
files = variant.get('files', [])

extra_data = {}
if gradle_version:
extra_data['gradle_version'] = gradle_version
format_version = data.get('formatVersion')
if format_version:
extra_data['format_version'] = format_version

package_data = dict(
datasource_id=cls.datasource_id,
type=cls.default_package_type,
namespace=namespace,
name=name,
version=version,
dependencies=list(seen_deps.values()),
extra_data=extra_data,
)

# Store file checksums on the PackageData if available from the first variant files
for f in files:
for algo in ('sha1', 'sha256', 'sha512', 'md5'):
if f.get(algo):
package_data[algo] = f.get(algo)
break

yield models.PackageData.from_data(package_data, package_only)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"formatVersion": "1.1",
"component": {
"group": "io.spring.gradle",
"module": "dependency-management-plugin",
"version": "1.1.3",
"attributes": {
"org.gradle.status": "release"
}
},
"createdBy": {
"gradle": {
"version": "7.6.1"
}
},
"variants": [
{
"name": "apiElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.usage": "java-api"
},
"dependencies": [
{
"group": "org.springframework.boot",
"module": "spring-boot",
"version": {
"requires": "3.1.0"
}
}
],
"files": [
{
"name": "dependency-management-plugin-1.1.3.jar",
"url": "dependency-management-plugin-1.1.3.jar",
"sha1": "3209385654a7e661d68de95a5ea8fc11d8ce015e",
"md5": "abc123"
}
]
}
]
}
74 changes: 74 additions & 0 deletions tests/packagedcode/data/gradle/module/material-1.9.0.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"formatVersion": "1.1",
"component": {
"group": "com.google.android.material",
"module": "material",
"version": "1.9.0",
"attributes": {
"org.gradle.status": "release"
}
},
"createdBy": {
"gradle": {
"version": "7.3.3"
}
},
"variants": [
{
"name": "releaseApiElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.usage": "java-api"
},
"dependencies": [
{
"group": "androidx.annotation",
"module": "annotation",
"version": {
"requires": "1.2.0"
}
},
{
"group": "androidx.appcompat",
"module": "appcompat",
"version": {
"requires": "1.5.0"
}
}
],
"files": [
{
"name": "material-1.9.0.aar",
"url": "material-1.9.0.aar",
"sha512": "7630aacb9e3073b2064397ed080b8d5bf7db06ba2022d6c927e05b7d53c5787d",
"sha256": "6cc2359979269e4d9eddce7d84682d2bb06a35a14edce806bf0da6e8d4d31806",
"sha1": "08f4a93a381be223a5bbaacd46eaab92381ab6a8",
"md5": "3287103cfb083fb998a35ef8a1983c58"
}
]
},
{
"name": "releaseRuntimeElements",
"attributes": {
"org.gradle.category": "library",
"org.gradle.usage": "java-runtime"
},
"dependencies": [
{
"group": "com.google.errorprone",
"module": "error_prone_annotations",
"version": {
"requires": "2.15.0"
}
},
{
"group": "androidx.annotation",
"module": "annotation",
"version": {
"requires": "1.2.0"
}
}
]
}
]
}
16 changes: 16 additions & 0 deletions tests/packagedcode/data/gradle/module/no-deps.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"formatVersion": "1.1",
"component": {
"group": "org.example",
"module": "standalone",
"version": "2.0.0"
},
"variants": [
{
"name": "apiElements",
"attributes": {},
"dependencies": [],
"files": []
}
]
}
9 changes: 9 additions & 0 deletions tests/packagedcode/data/gradle/module/simple.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"formatVersion": "1.1",
"component": {
"group": "com.example",
"module": "mylib",
"version": "1.0.0"
},
"variants": []
}
Loading
Loading