forked from microsoft/vscode-python
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__main__.py
More file actions
119 lines (105 loc) · 4.35 KB
/
__main__.py
File metadata and controls
119 lines (105 loc) · 4.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
"""Third-party notices generation.
Usage: tpn [--npm=<package-lock.json>] [--npm-overrides=<webpack-overrides.json>] --config=<TPN.toml> <tpn_path>
Options:
--npm=<package-lock.json> Path to a package-lock.json for npm.
--npm-overrides=<webpack-overrides.json> Path to a JSON file containing an array of names to override "dev" in <package-lock.json>.
--config=<TPN.toml> Path to the configuration file.
"""
import asyncio
import json
import pathlib
import sys
import textwrap
import docopt
import pytoml as toml
from . import config
from . import tpnfile
from . import npm
ACCEPTABLE_PURPOSES = frozenset({"explicit", "npm", "PyPI"})
async def handle_index(module, raw_path, config_projects, cached_projects, overrides_path=None):
_, _, index_name = module.__name__.rpartition(".")
with open(raw_path, encoding="utf-8") as file:
raw_data = file.read()
if overrides_path:
with open(overrides_path, encoding="utf-8") as file:
raw_overrides_data = file.read()
else:
raw_overrides_data = None
requested_projects = await module.projects_from_data(raw_data, raw_overrides_data)
projects, stale = config.sort(index_name, config_projects, requested_projects)
for name, details in projects.items():
print(f"{name} {details.version}: sourced from configuration file")
valid_cache_entries = tpnfile.sort(cached_projects, requested_projects)
for name, details in valid_cache_entries.items():
print(f"{name} {details.version}: sourced from TPN cache")
projects.update(valid_cache_entries)
failures = await module.fill_in_licenses(requested_projects)
projects.update(requested_projects)
# Check if a project which is stale by version is actually unneeded.
for stale_project in stale.keys():
if stale_project in projects:
stale[stale_project].error = config.UnneededEntry(stale_project)
return projects, stale, failures
def main(tpn_path, *, config_path, npm_path=None, npm_overrides=None, pypi_path=None):
tpn_path = pathlib.Path(tpn_path)
config_path = pathlib.Path(config_path)
config_data = toml.loads(config_path.read_text(encoding="utf-8"))
config_projects = config.get_projects(config_data, ACCEPTABLE_PURPOSES)
projects = config.get_explicit_entries(config_projects)
if tpn_path.exists():
cached_projects = tpnfile.parse_tpn(tpn_path.read_text(encoding="utf-8"))
else:
cached_projects = {}
tasks = []
if npm_path:
tasks.append(handle_index(npm, npm_path, config_projects, cached_projects, npm_overrides))
if pypi_path:
tasks.append(handle_index(pypi, pypi_path, config_projects, cached_projects))
loop = asyncio.get_event_loop()
print()
gathered = loop.run_until_complete(asyncio.gather(*tasks))
print()
stale = {}
failures = {}
for found_projects, found_stale, found_failures in gathered:
projects.update(found_projects)
stale.update(found_stale)
failures.update(found_failures)
if stale:
print("STALE ", end="")
print("*" * 20)
for name, details in stale.items():
print(details.error)
if failures:
print("FAILURES ", end="")
print("*" * 20) # Make failure stand out more.
for name, details in failures.items():
print(f"{name!r} {details.version} @ {details.url}: {details.error}")
print(f"NPM URL: {details.npm}")
print(textwrap.dedent(f"""
[[project]]
name = "{name}"
version = "{details.version}"
url = "{details.url}"
purpose = "{details.purpose or "XXX"}"
license = \"\"\"
TODO
\"\"\"
"""))
print()
print(f"Could not find a license for {len(failures)} projects")
if stale or failures:
sys.exit(1)
else:
with open(tpn_path, "w", encoding="utf-8", newline="\n") as file:
file.write(tpnfile.generate_tpn(config_data, projects))
if __name__ == "__main__":
arguments = docopt.docopt(__doc__)
main(
arguments["<tpn_path>"],
config_path=arguments["--config"],
npm_path=arguments["--npm"],
npm_overrides=arguments["--npm-overrides"],
)