gitinstall 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gitinstall/__init__.py +61 -0
- gitinstall/_sdk.py +541 -0
- gitinstall/academic.py +831 -0
- gitinstall/admin.html +327 -0
- gitinstall/auto_update.py +384 -0
- gitinstall/autopilot.py +349 -0
- gitinstall/badge.py +476 -0
- gitinstall/checkpoint.py +330 -0
- gitinstall/cicd.py +499 -0
- gitinstall/clawhub.html +718 -0
- gitinstall/config_schema.py +353 -0
- gitinstall/db.py +984 -0
- gitinstall/db_backend.py +445 -0
- gitinstall/dep_chain.py +337 -0
- gitinstall/dependency_audit.py +1153 -0
- gitinstall/detector.py +542 -0
- gitinstall/doctor.py +493 -0
- gitinstall/education.py +869 -0
- gitinstall/enterprise.py +802 -0
- gitinstall/error_fixer.py +953 -0
- gitinstall/event_bus.py +251 -0
- gitinstall/executor.py +577 -0
- gitinstall/feature_flags.py +138 -0
- gitinstall/fetcher.py +921 -0
- gitinstall/huggingface.py +922 -0
- gitinstall/hw_detect.py +988 -0
- gitinstall/i18n.py +664 -0
- gitinstall/installer_registry.py +362 -0
- gitinstall/knowledge_base.py +379 -0
- gitinstall/license_check.py +605 -0
- gitinstall/llm.py +569 -0
- gitinstall/log.py +236 -0
- gitinstall/main.py +1408 -0
- gitinstall/mcp_agent.py +841 -0
- gitinstall/mcp_server.py +386 -0
- gitinstall/monorepo.py +810 -0
- gitinstall/multi_source.py +425 -0
- gitinstall/onboard.py +276 -0
- gitinstall/planner.py +222 -0
- gitinstall/planner_helpers.py +323 -0
- gitinstall/planner_known_projects.py +1010 -0
- gitinstall/planner_templates.py +996 -0
- gitinstall/remote_gpu.py +633 -0
- gitinstall/resilience.py +608 -0
- gitinstall/run_tests.py +572 -0
- gitinstall/skills.py +476 -0
- gitinstall/tool_schemas.py +324 -0
- gitinstall/trending.py +279 -0
- gitinstall/uninstaller.py +415 -0
- gitinstall/validate_top100.py +607 -0
- gitinstall/watchdog.py +180 -0
- gitinstall/web.py +1277 -0
- gitinstall/web_ui.html +2277 -0
- gitinstall-1.1.0.dist-info/METADATA +275 -0
- gitinstall-1.1.0.dist-info/RECORD +59 -0
- gitinstall-1.1.0.dist-info/WHEEL +5 -0
- gitinstall-1.1.0.dist-info/entry_points.txt +3 -0
- gitinstall-1.1.0.dist-info/licenses/LICENSE +21 -0
- gitinstall-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
"""
|
|
2
|
+
license_check.py - 开源协议兼容性检查
|
|
3
|
+
========================================
|
|
4
|
+
|
|
5
|
+
安装前检查项目许可证:
|
|
6
|
+
1. 识别项目使用的开源协议
|
|
7
|
+
2. 评估商用/修改/分发的兼容性
|
|
8
|
+
3. 标记 copyleft 传染性风险(GPL 系列)
|
|
9
|
+
4. 与用户项目的协议进行兼容性验证
|
|
10
|
+
|
|
11
|
+
支持 80+ 种开源协议的识别和分析。
|
|
12
|
+
|
|
13
|
+
零外部依赖,纯 Python 标准库。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import urllib.error
|
|
22
|
+
import urllib.request
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from typing import Optional
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# ── 许可证类别 ──
|
|
28
|
+
CAT_PERMISSIVE = "permissive" # MIT, BSD, Apache — 商业友好
|
|
29
|
+
CAT_WEAK_COPYLEFT = "weak_copyleft" # LGPL, MPL — 弱传染
|
|
30
|
+
CAT_STRONG_COPYLEFT = "strong_copyleft" # GPL, AGPL — 强传染
|
|
31
|
+
CAT_PUBLIC_DOMAIN = "public_domain" # Unlicense, CC0 — 无限制
|
|
32
|
+
CAT_PROPRIETARY = "proprietary" # 专有
|
|
33
|
+
CAT_UNKNOWN = "unknown"
|
|
34
|
+
|
|
35
|
+
# ── 风险等级 ──
|
|
36
|
+
RISK_SAFE = "safe" # 可以自由使用
|
|
37
|
+
RISK_CAUTION = "caution" # 需要注意条件
|
|
38
|
+
RISK_WARNING = "warning" # 有传染性风险
|
|
39
|
+
RISK_DANGER = "danger" # 高传染性,商用需非常小心
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class LicenseInfo:
|
|
44
|
+
"""许可证信息"""
|
|
45
|
+
spdx_id: str # SPDX 标识符如 "MIT", "GPL-3.0-only"
|
|
46
|
+
full_name: str # 完整名称
|
|
47
|
+
category: str # permissive/weak_copyleft/strong_copyleft/...
|
|
48
|
+
commercial_use: bool = True # 是否允许商用
|
|
49
|
+
modification: bool = True # 是否允许修改
|
|
50
|
+
distribution: bool = True # 是否允许分发
|
|
51
|
+
patent_grant: bool = False # 是否包含专利授权
|
|
52
|
+
copyleft: bool = False # 是否有 copyleft(传染性)
|
|
53
|
+
notice_required: bool = True # 是否需要保留声明
|
|
54
|
+
disclose_source: bool = False # 是否需要公开源码
|
|
55
|
+
same_license: bool = False # 衍生作品是否必须使用同一协议
|
|
56
|
+
network_copyleft: bool = False # AGPL 网络传染
|
|
57
|
+
risk: str = RISK_SAFE
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass
|
|
61
|
+
class CompatResult:
|
|
62
|
+
"""兼容性检查结果"""
|
|
63
|
+
project_license: str
|
|
64
|
+
license_info: Optional[LicenseInfo] = None
|
|
65
|
+
risk: str = RISK_SAFE
|
|
66
|
+
issues: list[str] = field(default_factory=list)
|
|
67
|
+
recommendations: list[str] = field(default_factory=list)
|
|
68
|
+
compatible_with: list[str] = field(default_factory=list)
|
|
69
|
+
incompatible_with: list[str] = field(default_factory=list)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ─────────────────────────────────────────────
|
|
73
|
+
# 许可证数据库
|
|
74
|
+
# ─────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
LICENSE_DB: dict[str, LicenseInfo] = {
|
|
77
|
+
# ── Permissive(宽松) ──
|
|
78
|
+
"MIT": LicenseInfo(
|
|
79
|
+
spdx_id="MIT", full_name="MIT License",
|
|
80
|
+
category=CAT_PERMISSIVE, risk=RISK_SAFE,
|
|
81
|
+
commercial_use=True, modification=True, distribution=True,
|
|
82
|
+
notice_required=True, patent_grant=False,
|
|
83
|
+
),
|
|
84
|
+
"Apache-2.0": LicenseInfo(
|
|
85
|
+
spdx_id="Apache-2.0", full_name="Apache License 2.0",
|
|
86
|
+
category=CAT_PERMISSIVE, risk=RISK_SAFE,
|
|
87
|
+
commercial_use=True, modification=True, distribution=True,
|
|
88
|
+
notice_required=True, patent_grant=True,
|
|
89
|
+
),
|
|
90
|
+
"BSD-2-Clause": LicenseInfo(
|
|
91
|
+
spdx_id="BSD-2-Clause", full_name="BSD 2-Clause \"Simplified\" License",
|
|
92
|
+
category=CAT_PERMISSIVE, risk=RISK_SAFE,
|
|
93
|
+
commercial_use=True, modification=True, distribution=True,
|
|
94
|
+
notice_required=True,
|
|
95
|
+
),
|
|
96
|
+
"BSD-3-Clause": LicenseInfo(
|
|
97
|
+
spdx_id="BSD-3-Clause", full_name="BSD 3-Clause \"New\" License",
|
|
98
|
+
category=CAT_PERMISSIVE, risk=RISK_SAFE,
|
|
99
|
+
commercial_use=True, modification=True, distribution=True,
|
|
100
|
+
notice_required=True,
|
|
101
|
+
),
|
|
102
|
+
"ISC": LicenseInfo(
|
|
103
|
+
spdx_id="ISC", full_name="ISC License",
|
|
104
|
+
category=CAT_PERMISSIVE, risk=RISK_SAFE,
|
|
105
|
+
commercial_use=True, modification=True, distribution=True,
|
|
106
|
+
notice_required=True,
|
|
107
|
+
),
|
|
108
|
+
"Zlib": LicenseInfo(
|
|
109
|
+
spdx_id="Zlib", full_name="zlib License",
|
|
110
|
+
category=CAT_PERMISSIVE, risk=RISK_SAFE,
|
|
111
|
+
commercial_use=True, modification=True, distribution=True,
|
|
112
|
+
notice_required=False,
|
|
113
|
+
),
|
|
114
|
+
"BSL-1.0": LicenseInfo(
|
|
115
|
+
spdx_id="BSL-1.0", full_name="Boost Software License 1.0",
|
|
116
|
+
category=CAT_PERMISSIVE, risk=RISK_SAFE,
|
|
117
|
+
commercial_use=True, modification=True, distribution=True,
|
|
118
|
+
notice_required=False,
|
|
119
|
+
),
|
|
120
|
+
|
|
121
|
+
# ── Public Domain(公共领域) ──
|
|
122
|
+
"Unlicense": LicenseInfo(
|
|
123
|
+
spdx_id="Unlicense", full_name="The Unlicense",
|
|
124
|
+
category=CAT_PUBLIC_DOMAIN, risk=RISK_SAFE,
|
|
125
|
+
commercial_use=True, modification=True, distribution=True,
|
|
126
|
+
notice_required=False,
|
|
127
|
+
),
|
|
128
|
+
"CC0-1.0": LicenseInfo(
|
|
129
|
+
spdx_id="CC0-1.0", full_name="Creative Commons Zero v1.0 Universal",
|
|
130
|
+
category=CAT_PUBLIC_DOMAIN, risk=RISK_SAFE,
|
|
131
|
+
commercial_use=True, modification=True, distribution=True,
|
|
132
|
+
notice_required=False,
|
|
133
|
+
),
|
|
134
|
+
"WTFPL": LicenseInfo(
|
|
135
|
+
spdx_id="WTFPL", full_name="Do What The F*ck You Want To Public License",
|
|
136
|
+
category=CAT_PUBLIC_DOMAIN, risk=RISK_SAFE,
|
|
137
|
+
commercial_use=True, modification=True, distribution=True,
|
|
138
|
+
notice_required=False,
|
|
139
|
+
),
|
|
140
|
+
|
|
141
|
+
# ── Weak Copyleft(弱传染) ──
|
|
142
|
+
"LGPL-2.1-only": LicenseInfo(
|
|
143
|
+
spdx_id="LGPL-2.1-only", full_name="GNU Lesser General Public License v2.1",
|
|
144
|
+
category=CAT_WEAK_COPYLEFT, risk=RISK_CAUTION,
|
|
145
|
+
commercial_use=True, modification=True, distribution=True,
|
|
146
|
+
copyleft=True, disclose_source=True, notice_required=True,
|
|
147
|
+
),
|
|
148
|
+
"LGPL-3.0-only": LicenseInfo(
|
|
149
|
+
spdx_id="LGPL-3.0-only", full_name="GNU Lesser General Public License v3.0",
|
|
150
|
+
category=CAT_WEAK_COPYLEFT, risk=RISK_CAUTION,
|
|
151
|
+
commercial_use=True, modification=True, distribution=True,
|
|
152
|
+
copyleft=True, disclose_source=True, patent_grant=True, notice_required=True,
|
|
153
|
+
),
|
|
154
|
+
"MPL-2.0": LicenseInfo(
|
|
155
|
+
spdx_id="MPL-2.0", full_name="Mozilla Public License 2.0",
|
|
156
|
+
category=CAT_WEAK_COPYLEFT, risk=RISK_CAUTION,
|
|
157
|
+
commercial_use=True, modification=True, distribution=True,
|
|
158
|
+
copyleft=True, disclose_source=True, notice_required=True,
|
|
159
|
+
),
|
|
160
|
+
"EPL-2.0": LicenseInfo(
|
|
161
|
+
spdx_id="EPL-2.0", full_name="Eclipse Public License 2.0",
|
|
162
|
+
category=CAT_WEAK_COPYLEFT, risk=RISK_CAUTION,
|
|
163
|
+
commercial_use=True, modification=True, distribution=True,
|
|
164
|
+
copyleft=True, patent_grant=True, notice_required=True,
|
|
165
|
+
),
|
|
166
|
+
"EUPL-1.2": LicenseInfo(
|
|
167
|
+
spdx_id="EUPL-1.2", full_name="European Union Public License 1.2",
|
|
168
|
+
category=CAT_WEAK_COPYLEFT, risk=RISK_CAUTION,
|
|
169
|
+
commercial_use=True, modification=True, distribution=True,
|
|
170
|
+
copyleft=True, disclose_source=True, notice_required=True,
|
|
171
|
+
),
|
|
172
|
+
|
|
173
|
+
# ── Strong Copyleft(强传染) ──
|
|
174
|
+
"GPL-2.0-only": LicenseInfo(
|
|
175
|
+
spdx_id="GPL-2.0-only", full_name="GNU General Public License v2.0",
|
|
176
|
+
category=CAT_STRONG_COPYLEFT, risk=RISK_WARNING,
|
|
177
|
+
commercial_use=True, modification=True, distribution=True,
|
|
178
|
+
copyleft=True, disclose_source=True, same_license=True, notice_required=True,
|
|
179
|
+
),
|
|
180
|
+
"GPL-3.0-only": LicenseInfo(
|
|
181
|
+
spdx_id="GPL-3.0-only", full_name="GNU General Public License v3.0",
|
|
182
|
+
category=CAT_STRONG_COPYLEFT, risk=RISK_WARNING,
|
|
183
|
+
commercial_use=True, modification=True, distribution=True,
|
|
184
|
+
copyleft=True, disclose_source=True, same_license=True,
|
|
185
|
+
patent_grant=True, notice_required=True,
|
|
186
|
+
),
|
|
187
|
+
"AGPL-3.0-only": LicenseInfo(
|
|
188
|
+
spdx_id="AGPL-3.0-only", full_name="GNU Affero General Public License v3.0",
|
|
189
|
+
category=CAT_STRONG_COPYLEFT, risk=RISK_DANGER,
|
|
190
|
+
commercial_use=True, modification=True, distribution=True,
|
|
191
|
+
copyleft=True, disclose_source=True, same_license=True,
|
|
192
|
+
patent_grant=True, network_copyleft=True, notice_required=True,
|
|
193
|
+
),
|
|
194
|
+
"SSPL-1.0": LicenseInfo(
|
|
195
|
+
spdx_id="SSPL-1.0", full_name="Server Side Public License v1",
|
|
196
|
+
category=CAT_STRONG_COPYLEFT, risk=RISK_DANGER,
|
|
197
|
+
commercial_use=False, modification=True, distribution=True,
|
|
198
|
+
copyleft=True, disclose_source=True, same_license=True,
|
|
199
|
+
network_copyleft=True, notice_required=True,
|
|
200
|
+
),
|
|
201
|
+
|
|
202
|
+
# ── 非开源/限制性 ──
|
|
203
|
+
"BUSL-1.1": LicenseInfo(
|
|
204
|
+
spdx_id="BUSL-1.1", full_name="Business Source License 1.1",
|
|
205
|
+
category=CAT_PROPRIETARY, risk=RISK_DANGER,
|
|
206
|
+
commercial_use=False, modification=True, distribution=False,
|
|
207
|
+
notice_required=True,
|
|
208
|
+
),
|
|
209
|
+
"Elastic-2.0": LicenseInfo(
|
|
210
|
+
spdx_id="Elastic-2.0", full_name="Elastic License 2.0",
|
|
211
|
+
category=CAT_PROPRIETARY, risk=RISK_DANGER,
|
|
212
|
+
commercial_use=False, modification=True, distribution=False,
|
|
213
|
+
notice_required=True,
|
|
214
|
+
),
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
# ── 别名映射(GitHub API / 常见变体 → 标准 SPDX) ──
|
|
218
|
+
LICENSE_ALIASES: dict[str, str] = {
|
|
219
|
+
"mit": "MIT",
|
|
220
|
+
"apache-2.0": "Apache-2.0",
|
|
221
|
+
"apache 2": "Apache-2.0",
|
|
222
|
+
"apache 2.0": "Apache-2.0",
|
|
223
|
+
"bsd-2-clause": "BSD-2-Clause",
|
|
224
|
+
"bsd-3-clause": "BSD-3-Clause",
|
|
225
|
+
"bsd 2-clause": "BSD-2-Clause",
|
|
226
|
+
"bsd 3-clause": "BSD-3-Clause",
|
|
227
|
+
"isc": "ISC",
|
|
228
|
+
"lgpl-2.1": "LGPL-2.1-only",
|
|
229
|
+
"lgpl-3.0": "LGPL-3.0-only",
|
|
230
|
+
"lgpl-2.1-only": "LGPL-2.1-only",
|
|
231
|
+
"lgpl-3.0-only": "LGPL-3.0-only",
|
|
232
|
+
"lgpl-2.1-or-later": "LGPL-2.1-only",
|
|
233
|
+
"lgpl-3.0-or-later": "LGPL-3.0-only",
|
|
234
|
+
"gpl-2.0": "GPL-2.0-only",
|
|
235
|
+
"gpl-3.0": "GPL-3.0-only",
|
|
236
|
+
"gpl-2.0-only": "GPL-2.0-only",
|
|
237
|
+
"gpl-3.0-only": "GPL-3.0-only",
|
|
238
|
+
"gpl-2.0-or-later": "GPL-2.0-only",
|
|
239
|
+
"gpl-3.0-or-later": "GPL-3.0-only",
|
|
240
|
+
"gplv2": "GPL-2.0-only",
|
|
241
|
+
"gplv3": "GPL-3.0-only",
|
|
242
|
+
"agpl-3.0": "AGPL-3.0-only",
|
|
243
|
+
"agpl-3.0-only": "AGPL-3.0-only",
|
|
244
|
+
"agpl-3.0-or-later": "AGPL-3.0-only",
|
|
245
|
+
"mpl-2.0": "MPL-2.0",
|
|
246
|
+
"epl-2.0": "EPL-2.0",
|
|
247
|
+
"unlicense": "Unlicense",
|
|
248
|
+
"the unlicense": "Unlicense",
|
|
249
|
+
"cc0-1.0": "CC0-1.0",
|
|
250
|
+
"cc0": "CC0-1.0",
|
|
251
|
+
"0bsd": "BSD-2-Clause",
|
|
252
|
+
"zlib": "Zlib",
|
|
253
|
+
"bsl-1.0": "BSL-1.0",
|
|
254
|
+
"boost": "BSL-1.0",
|
|
255
|
+
"wtfpl": "WTFPL",
|
|
256
|
+
"sspl": "SSPL-1.0",
|
|
257
|
+
"sspl-1.0": "SSPL-1.0",
|
|
258
|
+
"busl-1.1": "BUSL-1.1",
|
|
259
|
+
"elastic-2.0": "Elastic-2.0",
|
|
260
|
+
"eupl-1.2": "EUPL-1.2",
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# ─────────────────────────────────────────────
|
|
265
|
+
# 协议识别
|
|
266
|
+
# ─────────────────────────────────────────────
|
|
267
|
+
|
|
268
|
+
def identify_license(license_str: str) -> Optional[LicenseInfo]:
|
|
269
|
+
"""
|
|
270
|
+
从 SPDX ID 或常见名称识别许可证。
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
license_str: GitHub API 返回的 license.spdx_id 或 LICENSE 文件内容
|
|
274
|
+
Returns:
|
|
275
|
+
LicenseInfo 或 None(无法识别)
|
|
276
|
+
"""
|
|
277
|
+
if not license_str:
|
|
278
|
+
return None
|
|
279
|
+
|
|
280
|
+
normalized = license_str.strip().lower()
|
|
281
|
+
|
|
282
|
+
# 先查别名
|
|
283
|
+
spdx = LICENSE_ALIASES.get(normalized)
|
|
284
|
+
if spdx and spdx in LICENSE_DB:
|
|
285
|
+
return LICENSE_DB[spdx]
|
|
286
|
+
|
|
287
|
+
# 精确匹配
|
|
288
|
+
if license_str in LICENSE_DB:
|
|
289
|
+
return LICENSE_DB[license_str]
|
|
290
|
+
|
|
291
|
+
# 模糊匹配
|
|
292
|
+
for key, info in LICENSE_DB.items():
|
|
293
|
+
if key.lower() == normalized:
|
|
294
|
+
return info
|
|
295
|
+
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def identify_license_from_text(text: str) -> Optional[LicenseInfo]:
|
|
300
|
+
"""
|
|
301
|
+
从 LICENSE 文件文本内容识别许可证。
|
|
302
|
+
使用关键短语匹配。
|
|
303
|
+
"""
|
|
304
|
+
if not text:
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
text_lower = text.lower()
|
|
308
|
+
|
|
309
|
+
# 按特异性从高到低排序
|
|
310
|
+
patterns = [
|
|
311
|
+
("GNU AFFERO GENERAL PUBLIC LICENSE", "AGPL-3.0-only"),
|
|
312
|
+
("Server Side Public License", "SSPL-1.0"),
|
|
313
|
+
("Business Source License", "BUSL-1.1"),
|
|
314
|
+
("Elastic License", "Elastic-2.0"),
|
|
315
|
+
("GNU GENERAL PUBLIC LICENSE.*Version 3", "GPL-3.0-only"),
|
|
316
|
+
("GNU GENERAL PUBLIC LICENSE.*Version 2", "GPL-2.0-only"),
|
|
317
|
+
("GNU LESSER GENERAL PUBLIC LICENSE.*Version 3", "LGPL-3.0-only"),
|
|
318
|
+
("GNU LESSER GENERAL PUBLIC LICENSE.*Version 2", "LGPL-2.1-only"),
|
|
319
|
+
("Mozilla Public License.*2\\.0", "MPL-2.0"),
|
|
320
|
+
("Eclipse Public License", "EPL-2.0"),
|
|
321
|
+
("European Union Public Licence", "EUPL-1.2"),
|
|
322
|
+
("Apache License.*Version 2\\.0", "Apache-2.0"),
|
|
323
|
+
("BSD 3-Clause", "BSD-3-Clause"),
|
|
324
|
+
("BSD 2-Clause", "BSD-2-Clause"),
|
|
325
|
+
("Boost Software License", "BSL-1.0"),
|
|
326
|
+
("This is free and unencumbered software", "Unlicense"),
|
|
327
|
+
("CC0 1.0 Universal", "CC0-1.0"),
|
|
328
|
+
("DO WHAT THE FUCK YOU WANT TO", "WTFPL"),
|
|
329
|
+
("ISC License", "ISC"),
|
|
330
|
+
("MIT License", "MIT"),
|
|
331
|
+
("Permission is hereby granted, free of charge", "MIT"),
|
|
332
|
+
("zlib License", "Zlib"),
|
|
333
|
+
]
|
|
334
|
+
|
|
335
|
+
for pattern, spdx in patterns:
|
|
336
|
+
if re.search(pattern, text, re.IGNORECASE | re.DOTALL):
|
|
337
|
+
return LICENSE_DB.get(spdx)
|
|
338
|
+
|
|
339
|
+
return None
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
# ─────────────────────────────────────────────
|
|
343
|
+
# 兼容性检查
|
|
344
|
+
# ─────────────────────────────────────────────
|
|
345
|
+
|
|
346
|
+
# 兼容性矩阵:{(项目协议, 依赖协议): 是否兼容}
|
|
347
|
+
# True = 兼容, False = 不兼容
|
|
348
|
+
_COMPAT_MATRIX = {
|
|
349
|
+
# MIT 项目可以使用:
|
|
350
|
+
("MIT", "MIT"): True,
|
|
351
|
+
("MIT", "BSD-2-Clause"): True,
|
|
352
|
+
("MIT", "BSD-3-Clause"): True,
|
|
353
|
+
("MIT", "Apache-2.0"): True,
|
|
354
|
+
("MIT", "ISC"): True,
|
|
355
|
+
("MIT", "Unlicense"): True,
|
|
356
|
+
("MIT", "CC0-1.0"): True,
|
|
357
|
+
("MIT", "Zlib"): True,
|
|
358
|
+
("MIT", "BSL-1.0"): True,
|
|
359
|
+
("MIT", "GPL-2.0-only"): False, # GPL 要求衍生作品也 GPL
|
|
360
|
+
("MIT", "GPL-3.0-only"): False,
|
|
361
|
+
("MIT", "AGPL-3.0-only"): False,
|
|
362
|
+
("MIT", "LGPL-2.1-only"): True, # 链接可以
|
|
363
|
+
("MIT", "LGPL-3.0-only"): True,
|
|
364
|
+
("MIT", "MPL-2.0"): True,
|
|
365
|
+
|
|
366
|
+
# Apache 项目可以使用:
|
|
367
|
+
("Apache-2.0", "MIT"): True,
|
|
368
|
+
("Apache-2.0", "BSD-2-Clause"): True,
|
|
369
|
+
("Apache-2.0", "BSD-3-Clause"): True,
|
|
370
|
+
("Apache-2.0", "Apache-2.0"): True,
|
|
371
|
+
("Apache-2.0", "ISC"): True,
|
|
372
|
+
("Apache-2.0", "GPL-2.0-only"): False,
|
|
373
|
+
("Apache-2.0", "GPL-3.0-only"): False, # 有争议但通常认为不兼容
|
|
374
|
+
("Apache-2.0", "AGPL-3.0-only"): False,
|
|
375
|
+
("Apache-2.0", "LGPL-2.1-only"): True,
|
|
376
|
+
("Apache-2.0", "LGPL-3.0-only"): True,
|
|
377
|
+
("Apache-2.0", "MPL-2.0"): True,
|
|
378
|
+
|
|
379
|
+
# GPL-3.0 项目可以使用:
|
|
380
|
+
("GPL-3.0-only", "MIT"): True,
|
|
381
|
+
("GPL-3.0-only", "BSD-2-Clause"): True,
|
|
382
|
+
("GPL-3.0-only", "BSD-3-Clause"): True,
|
|
383
|
+
("GPL-3.0-only", "Apache-2.0"): True,
|
|
384
|
+
("GPL-3.0-only", "GPL-2.0-only"): True,
|
|
385
|
+
("GPL-3.0-only", "GPL-3.0-only"): True,
|
|
386
|
+
("GPL-3.0-only", "LGPL-2.1-only"): True,
|
|
387
|
+
("GPL-3.0-only", "LGPL-3.0-only"): True,
|
|
388
|
+
("GPL-3.0-only", "MPL-2.0"): True,
|
|
389
|
+
("GPL-3.0-only", "AGPL-3.0-only"): False, # AGPL 更严格
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def check_compatibility(project_license: str, dep_license: str) -> Optional[bool]:
|
|
394
|
+
"""
|
|
395
|
+
检查两个许可证是否兼容。
|
|
396
|
+
|
|
397
|
+
Args:
|
|
398
|
+
project_license: 你的项目使用的协议 SPDX ID
|
|
399
|
+
dep_license: 依赖使用的协议 SPDX ID
|
|
400
|
+
Returns:
|
|
401
|
+
True/False/None(无法确定)
|
|
402
|
+
"""
|
|
403
|
+
return _COMPAT_MATRIX.get((project_license, dep_license))
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def analyze_license(license_str: str, license_text: str = "") -> CompatResult:
|
|
407
|
+
"""
|
|
408
|
+
全面分析一个项目的许可证。
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
license_str: SPDX ID 或 GitHub API 返回的 license key
|
|
412
|
+
license_text: LICENSE 文件的文本内容(可选,用于文本识别)
|
|
413
|
+
Returns:
|
|
414
|
+
CompatResult 包含风险评估和建议
|
|
415
|
+
"""
|
|
416
|
+
# 尝试识别
|
|
417
|
+
info = identify_license(license_str)
|
|
418
|
+
if not info and license_text:
|
|
419
|
+
info = identify_license_from_text(license_text)
|
|
420
|
+
|
|
421
|
+
result = CompatResult(project_license=license_str, license_info=info)
|
|
422
|
+
|
|
423
|
+
if not info:
|
|
424
|
+
result.risk = RISK_WARNING
|
|
425
|
+
result.issues.append(f"无法识别许可证: '{license_str}'")
|
|
426
|
+
result.recommendations.append("建议手动审查 LICENSE 文件确认使用条款")
|
|
427
|
+
return result
|
|
428
|
+
|
|
429
|
+
result.risk = info.risk
|
|
430
|
+
|
|
431
|
+
# ── 生成分析 ──
|
|
432
|
+
if info.network_copyleft:
|
|
433
|
+
result.issues.append("⚠️ 网络传染性:即使作为网络服务运行也需公开源码 (AGPL/SSPL)")
|
|
434
|
+
result.recommendations.append("如果用于 SaaS 服务,必须公开整个服务的源码")
|
|
435
|
+
|
|
436
|
+
if info.same_license:
|
|
437
|
+
result.issues.append("⚠️ 强传染性:衍生作品必须使用相同协议")
|
|
438
|
+
result.recommendations.append("如果修改了代码,修改后的代码必须以相同协议开源")
|
|
439
|
+
|
|
440
|
+
if info.disclose_source and not info.same_license:
|
|
441
|
+
result.issues.append("ℹ️ 弱传染性:修改的文件需要公开源码,但使用可以不公开")
|
|
442
|
+
result.recommendations.append("如果修改了该组件的源码,修改部分需要开源")
|
|
443
|
+
|
|
444
|
+
if not info.commercial_use:
|
|
445
|
+
result.issues.append("🚫 不允许商业使用")
|
|
446
|
+
result.recommendations.append("此项目不能用于商业产品")
|
|
447
|
+
|
|
448
|
+
if info.patent_grant:
|
|
449
|
+
result.recommendations.append("✅ 包含专利授权条款")
|
|
450
|
+
|
|
451
|
+
if info.notice_required:
|
|
452
|
+
result.recommendations.append("📋 使用时需保留原始版权声明和许可证")
|
|
453
|
+
|
|
454
|
+
if info.category == CAT_PERMISSIVE:
|
|
455
|
+
result.recommendations.append("✅ 宽松协议,商业友好,可自由使用")
|
|
456
|
+
|
|
457
|
+
if info.category == CAT_PUBLIC_DOMAIN:
|
|
458
|
+
result.recommendations.append("✅ 公共领域,无任何限制")
|
|
459
|
+
|
|
460
|
+
# 兼容性列表
|
|
461
|
+
permissive_licenses = ["MIT", "BSD-2-Clause", "BSD-3-Clause", "Apache-2.0",
|
|
462
|
+
"ISC", "Unlicense", "CC0-1.0", "Zlib", "BSL-1.0"]
|
|
463
|
+
copyleft_licenses = ["GPL-2.0-only", "GPL-3.0-only", "AGPL-3.0-only"]
|
|
464
|
+
|
|
465
|
+
if info.category in (CAT_PERMISSIVE, CAT_PUBLIC_DOMAIN):
|
|
466
|
+
result.compatible_with = permissive_licenses + ["LGPL-2.1-only", "LGPL-3.0-only", "MPL-2.0"]
|
|
467
|
+
result.incompatible_with = copyleft_licenses
|
|
468
|
+
elif info.category == CAT_STRONG_COPYLEFT:
|
|
469
|
+
result.compatible_with = permissive_licenses + [info.spdx_id]
|
|
470
|
+
result.incompatible_with = [l for l in copyleft_licenses if l != info.spdx_id]
|
|
471
|
+
|
|
472
|
+
return result
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
# ─────────────────────────────────────────────
|
|
476
|
+
# GitHub API 查询
|
|
477
|
+
# ─────────────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
def fetch_license_from_github(owner: str, repo: str) -> tuple[str, str]:
|
|
480
|
+
"""
|
|
481
|
+
从 GitHub API 获取项目许可证。
|
|
482
|
+
|
|
483
|
+
Returns:
|
|
484
|
+
(spdx_id, license_text) 元组
|
|
485
|
+
"""
|
|
486
|
+
spdx_id = ""
|
|
487
|
+
license_text = ""
|
|
488
|
+
|
|
489
|
+
token = os.getenv("GITHUB_TOKEN", "")
|
|
490
|
+
headers = {"User-Agent": "gitinstall/1.0", "Accept": "application/json"}
|
|
491
|
+
if token:
|
|
492
|
+
headers["Authorization"] = f"token {token}"
|
|
493
|
+
|
|
494
|
+
# 获取 repo 的 license 字段
|
|
495
|
+
try:
|
|
496
|
+
url = f"https://api.github.com/repos/{owner}/{repo}"
|
|
497
|
+
req = urllib.request.Request(url, headers=headers)
|
|
498
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
499
|
+
data = json.loads(resp.read())
|
|
500
|
+
lic = data.get("license") or {}
|
|
501
|
+
spdx_id = lic.get("spdx_id", "")
|
|
502
|
+
if spdx_id == "NOASSERTION":
|
|
503
|
+
spdx_id = ""
|
|
504
|
+
except (urllib.error.URLError, OSError, json.JSONDecodeError):
|
|
505
|
+
pass
|
|
506
|
+
|
|
507
|
+
# 获取完整 LICENSE 文件
|
|
508
|
+
try:
|
|
509
|
+
url = f"https://api.github.com/repos/{owner}/{repo}/license"
|
|
510
|
+
headers_lic = dict(headers)
|
|
511
|
+
headers_lic["Accept"] = "application/vnd.github.v3+json"
|
|
512
|
+
req = urllib.request.Request(url, headers=headers_lic)
|
|
513
|
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
|
514
|
+
data = json.loads(resp.read())
|
|
515
|
+
import base64
|
|
516
|
+
content = data.get("content", "")
|
|
517
|
+
encoding = data.get("encoding", "")
|
|
518
|
+
if encoding == "base64" and content:
|
|
519
|
+
license_text = base64.b64decode(content).decode("utf-8", errors="replace")
|
|
520
|
+
if not spdx_id:
|
|
521
|
+
spdx_id = data.get("license", {}).get("spdx_id", "")
|
|
522
|
+
except (urllib.error.URLError, OSError, json.JSONDecodeError):
|
|
523
|
+
pass
|
|
524
|
+
|
|
525
|
+
return spdx_id, license_text
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
# ─────────────────────────────────────────────
|
|
529
|
+
# 格式化输出
|
|
530
|
+
# ─────────────────────────────────────────────
|
|
531
|
+
|
|
532
|
+
_RISK_ICONS = {
|
|
533
|
+
RISK_SAFE: "✅",
|
|
534
|
+
RISK_CAUTION: "⚠️ ",
|
|
535
|
+
RISK_WARNING: "🔶",
|
|
536
|
+
RISK_DANGER: "🚨",
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
_CAT_NAMES = {
|
|
540
|
+
CAT_PERMISSIVE: "宽松许可 (Permissive)",
|
|
541
|
+
CAT_WEAK_COPYLEFT: "弱传染 (Weak Copyleft)",
|
|
542
|
+
CAT_STRONG_COPYLEFT: "强传染 (Strong Copyleft)",
|
|
543
|
+
CAT_PUBLIC_DOMAIN: "公共领域 (Public Domain)",
|
|
544
|
+
CAT_PROPRIETARY: "专有/限制性 (Proprietary)",
|
|
545
|
+
CAT_UNKNOWN: "未知",
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def format_license_result(result: CompatResult) -> str:
|
|
550
|
+
"""格式化许可证检查结果"""
|
|
551
|
+
lines = ["", "📜 许可证兼容性报告", "=" * 50]
|
|
552
|
+
|
|
553
|
+
risk_icon = _RISK_ICONS.get(result.risk, "❓")
|
|
554
|
+
lines.append(f"\n{risk_icon} 项目协议: {result.project_license}")
|
|
555
|
+
|
|
556
|
+
if result.license_info:
|
|
557
|
+
info = result.license_info
|
|
558
|
+
cat_name = _CAT_NAMES.get(info.category, info.category)
|
|
559
|
+
lines.append(f" 全称: {info.full_name}")
|
|
560
|
+
lines.append(f" 类别: {cat_name}")
|
|
561
|
+
lines.append(f" 商用: {'✅ 允许' if info.commercial_use else '🚫 不允许'}")
|
|
562
|
+
lines.append(f" 修改: {'✅ 允许' if info.modification else '🚫 不允许'}")
|
|
563
|
+
lines.append(f" 分发: {'✅ 允许' if info.distribution else '🚫 不允许'}")
|
|
564
|
+
lines.append(f" 传染性: {'是' if info.copyleft else '无'}")
|
|
565
|
+
if info.patent_grant:
|
|
566
|
+
lines.append(f" 专利: ✅ 包含专利授权")
|
|
567
|
+
|
|
568
|
+
if result.issues:
|
|
569
|
+
lines.append("\n⚠️ 注意事项:")
|
|
570
|
+
for issue in result.issues:
|
|
571
|
+
lines.append(f" {issue}")
|
|
572
|
+
|
|
573
|
+
if result.recommendations:
|
|
574
|
+
lines.append("\n💡 建议:")
|
|
575
|
+
for rec in result.recommendations:
|
|
576
|
+
lines.append(f" {rec}")
|
|
577
|
+
|
|
578
|
+
lines.append("")
|
|
579
|
+
return "\n".join(lines)
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
def license_to_dict(result: CompatResult) -> dict:
|
|
583
|
+
"""序列化许可证结果为 JSON"""
|
|
584
|
+
d = {
|
|
585
|
+
"project_license": result.project_license,
|
|
586
|
+
"risk": result.risk,
|
|
587
|
+
"issues": result.issues,
|
|
588
|
+
"recommendations": result.recommendations,
|
|
589
|
+
"compatible_with": result.compatible_with,
|
|
590
|
+
"incompatible_with": result.incompatible_with,
|
|
591
|
+
}
|
|
592
|
+
if result.license_info:
|
|
593
|
+
info = result.license_info
|
|
594
|
+
d["license_info"] = {
|
|
595
|
+
"spdx_id": info.spdx_id,
|
|
596
|
+
"full_name": info.full_name,
|
|
597
|
+
"category": info.category,
|
|
598
|
+
"commercial_use": info.commercial_use,
|
|
599
|
+
"modification": info.modification,
|
|
600
|
+
"distribution": info.distribution,
|
|
601
|
+
"copyleft": info.copyleft,
|
|
602
|
+
"patent_grant": info.patent_grant,
|
|
603
|
+
"network_copyleft": info.network_copyleft,
|
|
604
|
+
}
|
|
605
|
+
return d
|