Skip to content

Commit d981a1c

Browse files
committed
Detect dependencies from build.gradle files
* Create parser for dependency info from build.gradle files Signed-off-by: Jono Yang <jyang@nexb.com>
1 parent 132e6b8 commit d981a1c

File tree

2 files changed

+106
-1
lines changed

2 files changed

+106
-1
lines changed

src/packagedcode/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,8 @@
107107
msi.MsiInstallerPackage,
108108
windows.MicrosoftUpdateManifest,
109109
pubspec.PubspecYaml,
110-
pubspec.PubspecLock
110+
pubspec.PubspecLock,
111+
build.BuildGradle,
111112
]
112113

113114
PACKAGE_MANIFESTS_BY_TYPE = {

src/packagedcode/build.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,16 @@
99

1010
from collections import defaultdict
1111
import ast
12+
import io
13+
import json
1214
import logging
1315
import os
1416

17+
from packageurl import PackageURL
18+
from pygments import highlight
19+
from pygments.formatter import Formatter
20+
from pygments.lexers import GroovyLexer
21+
from pygments.token import Token
1522
import attr
1623

1724
from commoncode import filetype
@@ -257,3 +264,100 @@ def compute_normalized_license(self):
257264

258265
if detected_licenses:
259266
return combine_expressions(detected_licenses)
267+
268+
269+
class BuildGradleFormatter(Formatter):
270+
def format(self, tokens, outfile):
271+
quoted = lambda x: (x.startswith('"') and x.endswith('"')) or (x.startswith("'") and value.endswith("'"))
272+
start_dependencies_block = False
273+
lines = []
274+
current_dep_line = []
275+
for ttype, value in tokens:
276+
if not start_dependencies_block:
277+
if ttype == Token.Name and value == 'dependencies':
278+
# If we are at the start of the 'dependencies' code block,
279+
# we continue executing the rest of the dependency
280+
# collection code within this for-loop
281+
start_dependencies_block = True
282+
continue
283+
else:
284+
# If we do not see the 'dependencies' block yet, we continue
285+
# and do not execute the code within this for-loop.
286+
continue
287+
288+
if ttype == Token.Name:
289+
current_dep_line.append(value)
290+
elif ttype in (Token.Literal.String.Single, Token.Literal.String.Double,):
291+
if quoted(value):
292+
value = value[1:-1]
293+
current_dep_line.append(value)
294+
elif ttype == Token.Text and value == '\n' and current_dep_line:
295+
# If we are looking at the end of a dependency declaration line,
296+
# append the info from the line we are looking at to our main collection
297+
# of dependency info and reset `current_dep_line` so we can
298+
# start collecting info for the next line
299+
lines.append(current_dep_line)
300+
current_dep_line = []
301+
elif ttype == Token.Operator and value == '}':
302+
# we are at the end of the dependencies block, so we can back out
303+
# and return the data we collected
304+
break
305+
else:
306+
# Ignore all other tokens and values that we do not care for
307+
continue
308+
json.dump(lines, outfile)
309+
310+
311+
def build_package(cls, dependencies):
312+
package_dependencies = []
313+
for dependency_line in dependencies:
314+
if len(dependency_line) != 2:
315+
continue
316+
317+
dependency_type, dependency = dependency_line
318+
namespace, name, version = dependency.split(':')
319+
320+
is_runtime = True
321+
is_optional = False
322+
if 'test' in dependency_type:
323+
is_runtime = False
324+
is_optional = True
325+
326+
package_dependencies.append(
327+
models.DependentPackage(
328+
purl=PackageURL(
329+
type='build.gradle',
330+
namespace=namespace,
331+
name=name,
332+
version=version
333+
).to_string(),
334+
scope='dependencies',
335+
requirement=version,
336+
is_runtime=is_runtime,
337+
is_optional=is_optional,
338+
)
339+
)
340+
341+
yield cls(
342+
dependencies=package_dependencies,
343+
)
344+
345+
346+
@attr.s()
347+
class BuildGradle(BaseBuildManifestPackage, models.PackageManifest):
348+
file_patterns = ('build.gradle',)
349+
extensions = ('.gradle',)
350+
# TODO: Not sure what the default type should be, change this to something
351+
# more appropriate later
352+
default_type = 'build.gradle'
353+
354+
@classmethod
355+
def recognize(cls, location):
356+
if not cls.is_manifest(location):
357+
return
358+
with io.open(location, encoding='utf-8') as loc:
359+
file_contents = loc.read()
360+
dependencies = highlight(
361+
file_contents, GroovyLexer(), BuildGradleFormatter())
362+
dependencies = json.loads(dependencies)
363+
return build_package(cls, dependencies)

0 commit comments

Comments
 (0)