src2purl 1.3.4__tar.gz → 1.4.0__tar.gz
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.
- src2purl-1.4.0/AUTHORS.md +9 -0
- {src2purl-1.3.4/src2purl.egg-info → src2purl-1.4.0}/PKG-INFO +103 -78
- {src2purl-1.3.4 → src2purl-1.4.0}/README.md +101 -77
- {src2purl-1.3.4 → src2purl-1.4.0}/pyproject.toml +1 -1
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/__init__.py +1 -1
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/orchestrator.py +27 -18
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/package_identifier.py +6 -2
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/integrations/oslili.py +63 -15
- {src2purl-1.3.4 → src2purl-1.4.0/src2purl.egg-info}/PKG-INFO +103 -78
- {src2purl-1.3.4 → src2purl-1.4.0}/src2purl.egg-info/SOURCES.txt +4 -1
- src2purl-1.4.0/tests/test_integration.py +35 -0
- src2purl-1.4.0/tests/test_src2purl.py +83 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/LICENSE +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/setup.cfg +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/cli/__init__.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/cli/main.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/cli/validate.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/__init__.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/cache.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/client.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/config.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/extractor.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/models.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/purl.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/scanner.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/scorer.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/subcomponent_detector.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/core/swhid.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/integrations/__init__.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/integrations/manifest_parser.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/integrations/upmex.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/search/__init__.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/search/hash_search.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/search/providers.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/search/strategies.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/utils/__init__.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2id/utils/datetime_utils.py +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2purl.egg-info/dependency_links.txt +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2purl.egg-info/entry_points.txt +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2purl.egg-info/requires.txt +0 -0
- {src2purl-1.3.4 → src2purl-1.4.0}/src2purl.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: src2purl
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Source to PURL - Identify package coordinates and PURLs from source code using UPMEX and multiple discovery strategies
|
|
5
5
|
Author-email: "Oscar Valenzuela B." <oscar.valenzuela.b@gmail.com>
|
|
6
6
|
License: AGPL-3.0
|
|
@@ -20,6 +20,7 @@ Classifier: Topic :: Software Development :: Version Control
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
+
License-File: AUTHORS.md
|
|
23
24
|
Requires-Dist: aiohttp>=3.9.0
|
|
24
25
|
Requires-Dist: click>=8.1.0
|
|
25
26
|
Requires-Dist: rich>=13.0.0
|
|
@@ -43,84 +44,48 @@ Provides-Extra: enhanced
|
|
|
43
44
|
Requires-Dist: osslili>=1.3.3; extra == "enhanced"
|
|
44
45
|
Dynamic: license-file
|
|
45
46
|
|
|
46
|
-
# SRC2PURL - Source Code to Package URL
|
|
47
|
+
# SRC2PURL - Source Code to Package URL Discovery
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
[](https://opensource.org/licenses/AGPL-3.0)
|
|
50
|
+
[](https://www.python.org/downloads/)
|
|
51
|
+
[](https://pypi.org/project/src2purl/)
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
src2purl uses a **progressive 2-phase discovery strategy** to identify packages:
|
|
53
|
-
|
|
54
|
-
### **Phase 1: Hash-based Package Discovery** (5-15 seconds)
|
|
55
|
-
**Primary identification using content fingerprinting and repository search:**
|
|
56
|
-
|
|
57
|
-
1. **Directory & Content Scanning** - Generate SWHIDs for directories and files
|
|
58
|
-
- ✅ **Complete file tree analysis** with configurable depth
|
|
59
|
-
- ✅ **SWHID generation** for precise content identification
|
|
60
|
-
- ✅ **Smart filtering** of binary files and hidden directories
|
|
61
|
-
|
|
62
|
-
2. **Software Heritage Archive** (Optional) - Deep provenance discovery
|
|
63
|
-
- ✅ **Most comprehensive** - finds exact source code matches
|
|
64
|
-
- ✅ **Historical accuracy** - can identify older versions
|
|
65
|
-
- ⚠️ **Requires opt-in** with `--use-swh` due to longer processing time
|
|
66
|
-
|
|
67
|
-
3. **Keyword Search** (Fallback) - Multi-platform repository discovery
|
|
68
|
-
- **GitHub API Search** - Repository identification by keywords
|
|
69
|
-
- **SCANOSS Fingerprinting** - Code similarity detection via file fingerprints
|
|
70
|
-
- ✅ **Universal coverage** - finds repositories for any project
|
|
71
|
-
- ✅ **Fast execution** (~10 seconds total)
|
|
72
|
-
|
|
73
|
-
### **Phase 2: Manifest-based Validation & Enhancement** (1-3 seconds)
|
|
74
|
-
**Authoritative package metadata extraction and result enhancement:**
|
|
75
|
-
|
|
76
|
-
4. **UPMEX Manifest Parsing** - Universal Package Metadata Extractor
|
|
77
|
-
- ✅ **Perfect metadata extraction** from package files (package.json, setup.py, pom.xml, go.mod, Cargo.toml, etc.)
|
|
78
|
-
- ✅ **Multi-ecosystem support** (PyPI, NPM, Maven, Go, Ruby Gems, PHP, .NET)
|
|
79
|
-
- ✅ **Complete package info** (name, version, license, PURL)
|
|
80
|
-
- ✅ **Cross-validation** with Phase 1 results for enhanced accuracy
|
|
53
|
+
A Python tool that identifies package coordinates (name, version, license, PURL) from source code directories using a hybrid discovery strategy with manifest parsing, code fingerprinting, repository search, and Software Heritage archive integration.
|
|
81
54
|
|
|
82
55
|
## Features
|
|
83
56
|
|
|
84
|
-
### **Core Capabilities**
|
|
85
57
|
- **2-Phase Discovery Strategy**: Hash-based identification enhanced by manifest parsing
|
|
86
58
|
- **Multi-Ecosystem Support**: PyPI, NPM, Maven, Go, Ruby Gems, PHP, .NET, and more
|
|
87
|
-
- **
|
|
88
|
-
- **
|
|
89
|
-
- **Package Coordinate Extraction**: Complete metadata (name, version, license, PURL)
|
|
90
|
-
|
|
91
|
-
### **Performance & Reliability**
|
|
92
|
-
- **Fast by Default**: 5-15 seconds for typical projects (vs 90+ seconds with SWH)
|
|
93
|
-
- **No API Keys Required**: Works well without authentication (SCANOSS, GitHub search)
|
|
94
|
-
- **Optional API Keys**: Enhanced rate limits and accuracy with GitHub/SCANOSS tokens
|
|
95
|
-
- **Persistent Caching**: File-based cache with smart TTL to avoid API rate limits
|
|
96
|
-
- **Rate Limit Handling**: Automatic backoff and retry logic
|
|
97
|
-
|
|
98
|
-
### **Discovery Methods**
|
|
99
|
-
- **UPMEX Manifest Parsing**: Universal Package Metadata Extractor for all major package ecosystems
|
|
100
|
-
- **SCANOSS Fingerprinting**: 100% accuracy code similarity with detailed license detection
|
|
101
|
-
- **GitHub Repository Search**: Universal coverage repository identification
|
|
102
|
-
- **Software Heritage Archive**: Comprehensive source inventory (opt-in with `--use-swh`)
|
|
103
|
-
|
|
104
|
-
### **Output & Integration**
|
|
105
|
-
- **Multiple Output Formats**: JSON and table output formats
|
|
106
|
-
- **PURL Generation**: Standard Package URLs for identified packages
|
|
107
|
-
- **Enhanced License Detection**: Integration with oslili for improved license detection
|
|
108
|
-
- **Subcomponent Detection**: Identifies multiple packages within monorepos and complex projects
|
|
59
|
+
- **Fast Performance**: 5-15 seconds for typical projects (vs 90+ seconds with SWH)
|
|
60
|
+
- **SEMCL.ONE Integration**: Works seamlessly with upmex, osslili, and other ecosystem tools
|
|
109
61
|
|
|
110
62
|
## Installation
|
|
111
63
|
|
|
112
|
-
|
|
64
|
+
```bash
|
|
65
|
+
pip install src2purl
|
|
66
|
+
```
|
|
113
67
|
|
|
68
|
+
For development:
|
|
114
69
|
```bash
|
|
115
|
-
git clone https://github.com/
|
|
70
|
+
git clone https://github.com/SemClone/src2purl.git
|
|
116
71
|
cd src2purl
|
|
117
72
|
pip install -e .
|
|
118
73
|
```
|
|
119
74
|
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Identify package from source code
|
|
79
|
+
src2purl /path/to/source/code
|
|
80
|
+
|
|
81
|
+
# With Software Heritage archive (comprehensive but slower)
|
|
82
|
+
src2purl /path/to/source --use-swh
|
|
83
|
+
```
|
|
84
|
+
|
|
120
85
|
|
|
121
86
|
## Usage
|
|
122
87
|
|
|
123
|
-
###
|
|
88
|
+
### CLI Usage
|
|
124
89
|
|
|
125
90
|
```bash
|
|
126
91
|
# Fast discovery (default) - Uses manifest parsing + SCANOSS + GitHub (5-15 seconds)
|
|
@@ -137,15 +102,24 @@ src2purl /path/to/source --output-format json
|
|
|
137
102
|
|
|
138
103
|
# Detect subcomponents in monorepos
|
|
139
104
|
src2purl /path/to/source --detect-subcomponents
|
|
105
|
+
```
|
|
140
106
|
|
|
141
|
-
|
|
142
|
-
src2purl /path/to/source --no-license-detection
|
|
107
|
+
### Python API
|
|
143
108
|
|
|
144
|
-
|
|
145
|
-
src2purl
|
|
109
|
+
```python
|
|
110
|
+
from src2purl import identify_package
|
|
146
111
|
|
|
147
|
-
#
|
|
148
|
-
|
|
112
|
+
# Basic identification
|
|
113
|
+
result = identify_package("/path/to/source")
|
|
114
|
+
print(f"Package: {result.name}@{result.version}")
|
|
115
|
+
print(f"PURL: {result.purl}")
|
|
116
|
+
|
|
117
|
+
# With options
|
|
118
|
+
result = identify_package(
|
|
119
|
+
path="/path/to/source",
|
|
120
|
+
use_swh=True,
|
|
121
|
+
confidence_threshold=0.85
|
|
122
|
+
)
|
|
149
123
|
```
|
|
150
124
|
|
|
151
125
|
### Discovery Strategy Examples
|
|
@@ -169,7 +143,7 @@ src2purl /path/to/project --confidence-threshold 0.85
|
|
|
169
143
|
|
|
170
144
|
### API Authentication
|
|
171
145
|
|
|
172
|
-
|
|
146
|
+
**No API keys required!** The tool works with the free public APIs. API keys only provide enhanced rate limits and additional features.
|
|
173
147
|
|
|
174
148
|
#### Recommended API Keys (Optional)
|
|
175
149
|
|
|
@@ -179,9 +153,9 @@ export GITHUB_TOKEN=your_github_personal_access_token
|
|
|
179
153
|
```
|
|
180
154
|
- **Get token**: https://github.com/settings/tokens (no special permissions needed)
|
|
181
155
|
- **Benefits**:
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
156
|
+
- **Rate limit**: 10 → 5000 requests/hour
|
|
157
|
+
- **Better search**: More accurate repository identification
|
|
158
|
+
- **No cost**: Completely free
|
|
185
159
|
- **Impact**: Significant improvement for repository discovery
|
|
186
160
|
|
|
187
161
|
**2. SCANOSS API** - **Nice to Have** (Free, optional)
|
|
@@ -190,9 +164,9 @@ export SCANOSS_API_KEY=your_scanoss_key
|
|
|
190
164
|
```
|
|
191
165
|
- **Get token**: Register at https://www.scanoss.com
|
|
192
166
|
- **Benefits**:
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
167
|
+
- **No cost**: Free tier available
|
|
168
|
+
- **Enhanced rate limits**: Premium API endpoint
|
|
169
|
+
- **Additional features**: Possible extra metadata
|
|
196
170
|
- **Impact**: Minor improvement (SCANOSS works great without key)
|
|
197
171
|
|
|
198
172
|
**3. Software Heritage API** - **For Heavy Usage** (Free, only if using `--use-swh`)
|
|
@@ -201,8 +175,8 @@ export SWH_API_TOKEN=your_swh_token
|
|
|
201
175
|
```
|
|
202
176
|
- **Get token**: Register at https://archive.softwareheritage.org/api/
|
|
203
177
|
- **Benefits**:
|
|
204
|
-
-
|
|
205
|
-
-
|
|
178
|
+
- **Bypass rate limits**: No 60-second waits
|
|
179
|
+
- **Faster comprehensive scans**: When using `--use-swh`
|
|
206
180
|
- **Impact**: Essential for `--use-swh` flag, not needed for default fast mode
|
|
207
181
|
|
|
208
182
|
#### Performance Comparison
|
|
@@ -270,10 +244,61 @@ src2purl /path/to/project --no-license-detection # Skip license enhancement
|
|
|
270
244
|
src2purl /path/to/project --use-swh --verbose # Full discovery with details
|
|
271
245
|
```
|
|
272
246
|
|
|
247
|
+
## Integration with SEMCL.ONE
|
|
248
|
+
|
|
249
|
+
SRC2PURL is a core component of the SEMCL.ONE ecosystem:
|
|
250
|
+
|
|
251
|
+
- Uses **upmex** for manifest parsing and package metadata extraction
|
|
252
|
+
- Integrates with **osslili** for enhanced license detection
|
|
253
|
+
- Provides PURLs for **purl2src** to download source packages
|
|
254
|
+
- Supports **purl2notices** for generating attribution documentation
|
|
255
|
+
- Enables **ospac** policy evaluation with identified packages
|
|
256
|
+
|
|
257
|
+
## Configuration
|
|
258
|
+
|
|
259
|
+
Configuration via environment variables:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# API tokens (optional but recommended)
|
|
263
|
+
export GITHUB_TOKEN=your_github_token
|
|
264
|
+
export SCANOSS_API_KEY=your_scanoss_key
|
|
265
|
+
export SWH_API_TOKEN=your_swh_token
|
|
266
|
+
|
|
267
|
+
# Performance settings
|
|
268
|
+
export SRC2PURL_CACHE_DIR=~/.cache/src2purl
|
|
269
|
+
export SRC2PURL_MAX_DEPTH=2
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Documentation
|
|
273
|
+
|
|
274
|
+
- [User Guide](docs/user-guide.md) - Comprehensive usage examples
|
|
275
|
+
- [API Reference](docs/api.md) - Python API documentation
|
|
276
|
+
- [Discovery Methods](docs/discovery-methods.md) - Detailed explanation of identification strategies
|
|
277
|
+
- [Examples](docs/examples.md) - Common use cases and workflows
|
|
278
|
+
|
|
279
|
+
## Contributing
|
|
280
|
+
|
|
281
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on:
|
|
282
|
+
- Code of conduct
|
|
283
|
+
- Development setup
|
|
284
|
+
- Submitting pull requests
|
|
285
|
+
- Reporting issues
|
|
286
|
+
|
|
287
|
+
## Support
|
|
288
|
+
|
|
289
|
+
For support and questions:
|
|
290
|
+
- [GitHub Issues](https://github.com/SemClone/src2purl/issues) - Bug reports and feature requests
|
|
291
|
+
- [Documentation](https://github.com/SemClone/src2purl) - Complete project documentation
|
|
292
|
+
- [SEMCL.ONE Community](https://semcl.one) - Ecosystem support and discussions
|
|
293
|
+
|
|
273
294
|
## License
|
|
274
295
|
|
|
275
|
-
|
|
296
|
+
GNU Affero General Public License v3.0 (AGPL-3.0) - see [LICENSE](LICENSE) file for details.
|
|
297
|
+
|
|
298
|
+
## Authors
|
|
299
|
+
|
|
300
|
+
See [AUTHORS.md](AUTHORS.md) for a list of contributors.
|
|
276
301
|
|
|
277
|
-
|
|
302
|
+
---
|
|
278
303
|
|
|
279
|
-
|
|
304
|
+
*Part of the [SEMCL.ONE](https://semcl.one) ecosystem for comprehensive OSS compliance and code analysis.*
|
|
@@ -1,81 +1,45 @@
|
|
|
1
|
-
# SRC2PURL - Source Code to Package URL
|
|
1
|
+
# SRC2PURL - Source Code to Package URL Discovery
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://opensource.org/licenses/AGPL-3.0)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://pypi.org/project/src2purl/)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
src2purl uses a **progressive 2-phase discovery strategy** to identify packages:
|
|
8
|
-
|
|
9
|
-
### **Phase 1: Hash-based Package Discovery** (5-15 seconds)
|
|
10
|
-
**Primary identification using content fingerprinting and repository search:**
|
|
11
|
-
|
|
12
|
-
1. **Directory & Content Scanning** - Generate SWHIDs for directories and files
|
|
13
|
-
- ✅ **Complete file tree analysis** with configurable depth
|
|
14
|
-
- ✅ **SWHID generation** for precise content identification
|
|
15
|
-
- ✅ **Smart filtering** of binary files and hidden directories
|
|
16
|
-
|
|
17
|
-
2. **Software Heritage Archive** (Optional) - Deep provenance discovery
|
|
18
|
-
- ✅ **Most comprehensive** - finds exact source code matches
|
|
19
|
-
- ✅ **Historical accuracy** - can identify older versions
|
|
20
|
-
- ⚠️ **Requires opt-in** with `--use-swh` due to longer processing time
|
|
21
|
-
|
|
22
|
-
3. **Keyword Search** (Fallback) - Multi-platform repository discovery
|
|
23
|
-
- **GitHub API Search** - Repository identification by keywords
|
|
24
|
-
- **SCANOSS Fingerprinting** - Code similarity detection via file fingerprints
|
|
25
|
-
- ✅ **Universal coverage** - finds repositories for any project
|
|
26
|
-
- ✅ **Fast execution** (~10 seconds total)
|
|
27
|
-
|
|
28
|
-
### **Phase 2: Manifest-based Validation & Enhancement** (1-3 seconds)
|
|
29
|
-
**Authoritative package metadata extraction and result enhancement:**
|
|
30
|
-
|
|
31
|
-
4. **UPMEX Manifest Parsing** - Universal Package Metadata Extractor
|
|
32
|
-
- ✅ **Perfect metadata extraction** from package files (package.json, setup.py, pom.xml, go.mod, Cargo.toml, etc.)
|
|
33
|
-
- ✅ **Multi-ecosystem support** (PyPI, NPM, Maven, Go, Ruby Gems, PHP, .NET)
|
|
34
|
-
- ✅ **Complete package info** (name, version, license, PURL)
|
|
35
|
-
- ✅ **Cross-validation** with Phase 1 results for enhanced accuracy
|
|
7
|
+
A Python tool that identifies package coordinates (name, version, license, PURL) from source code directories using a hybrid discovery strategy with manifest parsing, code fingerprinting, repository search, and Software Heritage archive integration.
|
|
36
8
|
|
|
37
9
|
## Features
|
|
38
10
|
|
|
39
|
-
### **Core Capabilities**
|
|
40
11
|
- **2-Phase Discovery Strategy**: Hash-based identification enhanced by manifest parsing
|
|
41
12
|
- **Multi-Ecosystem Support**: PyPI, NPM, Maven, Go, Ruby Gems, PHP, .NET, and more
|
|
42
|
-
- **
|
|
43
|
-
- **
|
|
44
|
-
- **Package Coordinate Extraction**: Complete metadata (name, version, license, PURL)
|
|
45
|
-
|
|
46
|
-
### **Performance & Reliability**
|
|
47
|
-
- **Fast by Default**: 5-15 seconds for typical projects (vs 90+ seconds with SWH)
|
|
48
|
-
- **No API Keys Required**: Works well without authentication (SCANOSS, GitHub search)
|
|
49
|
-
- **Optional API Keys**: Enhanced rate limits and accuracy with GitHub/SCANOSS tokens
|
|
50
|
-
- **Persistent Caching**: File-based cache with smart TTL to avoid API rate limits
|
|
51
|
-
- **Rate Limit Handling**: Automatic backoff and retry logic
|
|
52
|
-
|
|
53
|
-
### **Discovery Methods**
|
|
54
|
-
- **UPMEX Manifest Parsing**: Universal Package Metadata Extractor for all major package ecosystems
|
|
55
|
-
- **SCANOSS Fingerprinting**: 100% accuracy code similarity with detailed license detection
|
|
56
|
-
- **GitHub Repository Search**: Universal coverage repository identification
|
|
57
|
-
- **Software Heritage Archive**: Comprehensive source inventory (opt-in with `--use-swh`)
|
|
58
|
-
|
|
59
|
-
### **Output & Integration**
|
|
60
|
-
- **Multiple Output Formats**: JSON and table output formats
|
|
61
|
-
- **PURL Generation**: Standard Package URLs for identified packages
|
|
62
|
-
- **Enhanced License Detection**: Integration with oslili for improved license detection
|
|
63
|
-
- **Subcomponent Detection**: Identifies multiple packages within monorepos and complex projects
|
|
13
|
+
- **Fast Performance**: 5-15 seconds for typical projects (vs 90+ seconds with SWH)
|
|
14
|
+
- **SEMCL.ONE Integration**: Works seamlessly with upmex, osslili, and other ecosystem tools
|
|
64
15
|
|
|
65
16
|
## Installation
|
|
66
17
|
|
|
67
|
-
|
|
18
|
+
```bash
|
|
19
|
+
pip install src2purl
|
|
20
|
+
```
|
|
68
21
|
|
|
22
|
+
For development:
|
|
69
23
|
```bash
|
|
70
|
-
git clone https://github.com/
|
|
24
|
+
git clone https://github.com/SemClone/src2purl.git
|
|
71
25
|
cd src2purl
|
|
72
26
|
pip install -e .
|
|
73
27
|
```
|
|
74
28
|
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Identify package from source code
|
|
33
|
+
src2purl /path/to/source/code
|
|
34
|
+
|
|
35
|
+
# With Software Heritage archive (comprehensive but slower)
|
|
36
|
+
src2purl /path/to/source --use-swh
|
|
37
|
+
```
|
|
38
|
+
|
|
75
39
|
|
|
76
40
|
## Usage
|
|
77
41
|
|
|
78
|
-
###
|
|
42
|
+
### CLI Usage
|
|
79
43
|
|
|
80
44
|
```bash
|
|
81
45
|
# Fast discovery (default) - Uses manifest parsing + SCANOSS + GitHub (5-15 seconds)
|
|
@@ -92,15 +56,24 @@ src2purl /path/to/source --output-format json
|
|
|
92
56
|
|
|
93
57
|
# Detect subcomponents in monorepos
|
|
94
58
|
src2purl /path/to/source --detect-subcomponents
|
|
59
|
+
```
|
|
95
60
|
|
|
96
|
-
|
|
97
|
-
src2purl /path/to/source --no-license-detection
|
|
61
|
+
### Python API
|
|
98
62
|
|
|
99
|
-
|
|
100
|
-
src2purl
|
|
63
|
+
```python
|
|
64
|
+
from src2purl import identify_package
|
|
101
65
|
|
|
102
|
-
#
|
|
103
|
-
|
|
66
|
+
# Basic identification
|
|
67
|
+
result = identify_package("/path/to/source")
|
|
68
|
+
print(f"Package: {result.name}@{result.version}")
|
|
69
|
+
print(f"PURL: {result.purl}")
|
|
70
|
+
|
|
71
|
+
# With options
|
|
72
|
+
result = identify_package(
|
|
73
|
+
path="/path/to/source",
|
|
74
|
+
use_swh=True,
|
|
75
|
+
confidence_threshold=0.85
|
|
76
|
+
)
|
|
104
77
|
```
|
|
105
78
|
|
|
106
79
|
### Discovery Strategy Examples
|
|
@@ -124,7 +97,7 @@ src2purl /path/to/project --confidence-threshold 0.85
|
|
|
124
97
|
|
|
125
98
|
### API Authentication
|
|
126
99
|
|
|
127
|
-
|
|
100
|
+
**No API keys required!** The tool works with the free public APIs. API keys only provide enhanced rate limits and additional features.
|
|
128
101
|
|
|
129
102
|
#### Recommended API Keys (Optional)
|
|
130
103
|
|
|
@@ -134,9 +107,9 @@ export GITHUB_TOKEN=your_github_personal_access_token
|
|
|
134
107
|
```
|
|
135
108
|
- **Get token**: https://github.com/settings/tokens (no special permissions needed)
|
|
136
109
|
- **Benefits**:
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
-
|
|
110
|
+
- **Rate limit**: 10 → 5000 requests/hour
|
|
111
|
+
- **Better search**: More accurate repository identification
|
|
112
|
+
- **No cost**: Completely free
|
|
140
113
|
- **Impact**: Significant improvement for repository discovery
|
|
141
114
|
|
|
142
115
|
**2. SCANOSS API** - **Nice to Have** (Free, optional)
|
|
@@ -145,9 +118,9 @@ export SCANOSS_API_KEY=your_scanoss_key
|
|
|
145
118
|
```
|
|
146
119
|
- **Get token**: Register at https://www.scanoss.com
|
|
147
120
|
- **Benefits**:
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
121
|
+
- **No cost**: Free tier available
|
|
122
|
+
- **Enhanced rate limits**: Premium API endpoint
|
|
123
|
+
- **Additional features**: Possible extra metadata
|
|
151
124
|
- **Impact**: Minor improvement (SCANOSS works great without key)
|
|
152
125
|
|
|
153
126
|
**3. Software Heritage API** - **For Heavy Usage** (Free, only if using `--use-swh`)
|
|
@@ -156,8 +129,8 @@ export SWH_API_TOKEN=your_swh_token
|
|
|
156
129
|
```
|
|
157
130
|
- **Get token**: Register at https://archive.softwareheritage.org/api/
|
|
158
131
|
- **Benefits**:
|
|
159
|
-
-
|
|
160
|
-
-
|
|
132
|
+
- **Bypass rate limits**: No 60-second waits
|
|
133
|
+
- **Faster comprehensive scans**: When using `--use-swh`
|
|
161
134
|
- **Impact**: Essential for `--use-swh` flag, not needed for default fast mode
|
|
162
135
|
|
|
163
136
|
#### Performance Comparison
|
|
@@ -225,10 +198,61 @@ src2purl /path/to/project --no-license-detection # Skip license enhancement
|
|
|
225
198
|
src2purl /path/to/project --use-swh --verbose # Full discovery with details
|
|
226
199
|
```
|
|
227
200
|
|
|
201
|
+
## Integration with SEMCL.ONE
|
|
202
|
+
|
|
203
|
+
SRC2PURL is a core component of the SEMCL.ONE ecosystem:
|
|
204
|
+
|
|
205
|
+
- Uses **upmex** for manifest parsing and package metadata extraction
|
|
206
|
+
- Integrates with **osslili** for enhanced license detection
|
|
207
|
+
- Provides PURLs for **purl2src** to download source packages
|
|
208
|
+
- Supports **purl2notices** for generating attribution documentation
|
|
209
|
+
- Enables **ospac** policy evaluation with identified packages
|
|
210
|
+
|
|
211
|
+
## Configuration
|
|
212
|
+
|
|
213
|
+
Configuration via environment variables:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
# API tokens (optional but recommended)
|
|
217
|
+
export GITHUB_TOKEN=your_github_token
|
|
218
|
+
export SCANOSS_API_KEY=your_scanoss_key
|
|
219
|
+
export SWH_API_TOKEN=your_swh_token
|
|
220
|
+
|
|
221
|
+
# Performance settings
|
|
222
|
+
export SRC2PURL_CACHE_DIR=~/.cache/src2purl
|
|
223
|
+
export SRC2PURL_MAX_DEPTH=2
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Documentation
|
|
227
|
+
|
|
228
|
+
- [User Guide](docs/user-guide.md) - Comprehensive usage examples
|
|
229
|
+
- [API Reference](docs/api.md) - Python API documentation
|
|
230
|
+
- [Discovery Methods](docs/discovery-methods.md) - Detailed explanation of identification strategies
|
|
231
|
+
- [Examples](docs/examples.md) - Common use cases and workflows
|
|
232
|
+
|
|
233
|
+
## Contributing
|
|
234
|
+
|
|
235
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on:
|
|
236
|
+
- Code of conduct
|
|
237
|
+
- Development setup
|
|
238
|
+
- Submitting pull requests
|
|
239
|
+
- Reporting issues
|
|
240
|
+
|
|
241
|
+
## Support
|
|
242
|
+
|
|
243
|
+
For support and questions:
|
|
244
|
+
- [GitHub Issues](https://github.com/SemClone/src2purl/issues) - Bug reports and feature requests
|
|
245
|
+
- [Documentation](https://github.com/SemClone/src2purl) - Complete project documentation
|
|
246
|
+
- [SEMCL.ONE Community](https://semcl.one) - Ecosystem support and discussions
|
|
247
|
+
|
|
228
248
|
## License
|
|
229
249
|
|
|
230
|
-
|
|
250
|
+
GNU Affero General Public License v3.0 (AGPL-3.0) - see [LICENSE](LICENSE) file for details.
|
|
251
|
+
|
|
252
|
+
## Authors
|
|
253
|
+
|
|
254
|
+
See [AUTHORS.md](AUTHORS.md) for a list of contributors.
|
|
231
255
|
|
|
232
|
-
|
|
256
|
+
---
|
|
233
257
|
|
|
234
|
-
|
|
258
|
+
*Part of the [SEMCL.ONE](https://semcl.one) ecosystem for comprehensive OSS compliance and code analysis.*
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "src2purl"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.4.0"
|
|
8
8
|
description = "Source to PURL - Identify package coordinates and PURLs from source code using UPMEX and multiple discovery strategies"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -141,16 +141,6 @@ class SHPackageIdentifier:
|
|
|
141
141
|
if self.config.verbose:
|
|
142
142
|
console.print("[dim]Skipping Software Heritage (use --use-swh to enable)[/dim]")
|
|
143
143
|
|
|
144
|
-
if not all_matches:
|
|
145
|
-
# Try keyword search with GitHub and SCANOSS
|
|
146
|
-
if self.config.verbose:
|
|
147
|
-
console.print("[yellow]Trying web search (GitHub, SCANOSS)[/yellow]")
|
|
148
|
-
|
|
149
|
-
# Try keyword search
|
|
150
|
-
keyword_matches = await self._find_keyword_matches(path)
|
|
151
|
-
if keyword_matches:
|
|
152
|
-
all_matches = keyword_matches
|
|
153
|
-
|
|
154
144
|
# Step 1b: Process hash-based matches
|
|
155
145
|
if all_matches:
|
|
156
146
|
hash_based_matches = await self._process_matches(all_matches)
|
|
@@ -178,6 +168,27 @@ class SHPackageIdentifier:
|
|
|
178
168
|
|
|
179
169
|
# Final deduplication and sorting
|
|
180
170
|
final_matches = self._prioritize_and_deduplicate(enhanced_matches)
|
|
171
|
+
|
|
172
|
+
# Keyword search is the last resort, and only when it was asked for.
|
|
173
|
+
# It searches on the project name, so it answers with whatever else
|
|
174
|
+
# shares that name: running it for express returned ten npm packages
|
|
175
|
+
# scoring 0.83 against the real one at 0.85, close enough that a
|
|
176
|
+
# confidence threshold cannot separate them, and carrying no PURL so
|
|
177
|
+
# nothing downstream can act on them either.
|
|
178
|
+
#
|
|
179
|
+
# It also belongs here rather than in phase 1. The flag says fuzzy
|
|
180
|
+
# runs when exact matches fail, and whether they failed is not known
|
|
181
|
+
# until the manifest parsing in phase 2 has had its turn.
|
|
182
|
+
if not final_matches and self.config.enable_fuzzy_matching:
|
|
183
|
+
if self.config.verbose:
|
|
184
|
+
console.print(
|
|
185
|
+
"[yellow]Nothing identified - trying keyword search[/yellow]"
|
|
186
|
+
)
|
|
187
|
+
keyword_matches = await self._find_keyword_matches(path)
|
|
188
|
+
if keyword_matches:
|
|
189
|
+
final_matches = self._prioritize_and_deduplicate(
|
|
190
|
+
await self._process_matches(keyword_matches)
|
|
191
|
+
)
|
|
181
192
|
|
|
182
193
|
# Step 5: Optionally enhance with oslili license detection
|
|
183
194
|
if enhance_licenses:
|
|
@@ -588,14 +599,12 @@ class SHPackageIdentifier:
|
|
|
588
599
|
for candidate in non_existing_dirs:
|
|
589
600
|
console.print(f"[yellow]✗ No match for {candidate.path.name} ({candidate.swhid[:12]}...)[/yellow]")
|
|
590
601
|
|
|
591
|
-
#
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
all_matches.extend(keyword_matches)
|
|
598
|
-
|
|
602
|
+
# Keyword search is not run here. It used to be, and that put it before
|
|
603
|
+
# the manifest parsing in phase 2, so "when exact matches fail", which
|
|
604
|
+
# is what the flag promises, was decided before the step that finds the
|
|
605
|
+
# exact match had run. identify_packages runs it once, after everything
|
|
606
|
+
# else has had its turn.
|
|
607
|
+
|
|
599
608
|
# Report match summary
|
|
600
609
|
if self.config.verbose and (child_matches > 0 or parent_matches > 0):
|
|
601
610
|
console.print("\n[bold]Match Summary:[/bold]")
|
|
@@ -82,8 +82,12 @@ class PackageIdentifier:
|
|
|
82
82
|
|
|
83
83
|
if integration.available:
|
|
84
84
|
license_info = integration.detect_licenses(path)
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
# Only the project's own licenses identify this package;
|
|
86
|
+
# licenses from bundled third-party notice files belong
|
|
87
|
+
# to its dependencies
|
|
88
|
+
own_licenses = license_info.get("own_licenses") or []
|
|
89
|
+
if own_licenses:
|
|
90
|
+
match.license = ", ".join(own_licenses[:3])
|
|
87
91
|
except ImportError:
|
|
88
92
|
pass
|
|
89
93
|
|
|
@@ -59,14 +59,20 @@ class OsliliIntegration:
|
|
|
59
59
|
Returns:
|
|
60
60
|
Dictionary with license information:
|
|
61
61
|
- licenses: List of detected license identifiers
|
|
62
|
-
-
|
|
62
|
+
- own_licenses: Identifiers eligible to be the project's own license
|
|
63
|
+
- third_party_licenses: Identifiers from bundled third-party notice files
|
|
64
|
+
- confidence: Confidence score (0-1) across all detections
|
|
65
|
+
- own_confidence: Confidence score (0-1) across own licenses only
|
|
63
66
|
- files: Dict mapping files to their licenses
|
|
64
67
|
- summary: Human-readable summary
|
|
65
68
|
"""
|
|
66
69
|
if not self.available or not self.detector:
|
|
67
70
|
return {
|
|
68
71
|
"licenses": [],
|
|
72
|
+
"own_licenses": [],
|
|
73
|
+
"third_party_licenses": [],
|
|
69
74
|
"confidence": 0.0,
|
|
75
|
+
"own_confidence": 0.0,
|
|
70
76
|
"files": {},
|
|
71
77
|
"summary": "oslili not available",
|
|
72
78
|
"error": "oslili integration not available"
|
|
@@ -83,13 +89,19 @@ class OsliliIntegration:
|
|
|
83
89
|
files = {}
|
|
84
90
|
|
|
85
91
|
# Group licenses by category for better understanding
|
|
86
|
-
declared = [
|
|
87
|
-
detected = [
|
|
88
|
-
referenced = [
|
|
89
|
-
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
declared = [lic for lic in result.licenses if lic.category == "declared"]
|
|
93
|
+
detected = [lic for lic in result.licenses if lic.category == "detected"]
|
|
94
|
+
referenced = [lic for lic in result.licenses if lic.category == "referenced"]
|
|
95
|
+
# Licenses of bundled dependencies (THIRD_PARTY_NOTICES.txt and
|
|
96
|
+
# friends), not the project's own license. Kept, but ranked last so
|
|
97
|
+
# they never win the primary-license selection.
|
|
98
|
+
third_party = [lic for lic in result.licenses if lic.category == "third-party"]
|
|
99
|
+
|
|
100
|
+
# Prioritize declared licenses, then detected, then referenced,
|
|
101
|
+
# with bundled third-party licenses last
|
|
102
|
+
own = declared + detected + referenced
|
|
103
|
+
all_licenses = own + third_party
|
|
104
|
+
|
|
93
105
|
for license_info in all_licenses:
|
|
94
106
|
licenses.append(license_info.spdx_id)
|
|
95
107
|
confidence_scores.append(license_info.confidence)
|
|
@@ -107,6 +119,12 @@ class OsliliIntegration:
|
|
|
107
119
|
|
|
108
120
|
# Calculate average confidence
|
|
109
121
|
avg_confidence = sum(confidence_scores) / len(confidence_scores) if confidence_scores else 0.8
|
|
122
|
+
|
|
123
|
+
# Confidence in the project's own licenses, kept separate so a pile of
|
|
124
|
+
# high-confidence bundled notice files cannot inflate the score used to
|
|
125
|
+
# decide the package's own license
|
|
126
|
+
own_scores = [lic.confidence for lic in own]
|
|
127
|
+
own_confidence = sum(own_scores) / len(own_scores) if own_scores else 0.0
|
|
110
128
|
|
|
111
129
|
# Generate summary
|
|
112
130
|
if licenses:
|
|
@@ -126,7 +144,15 @@ class OsliliIntegration:
|
|
|
126
144
|
|
|
127
145
|
return {
|
|
128
146
|
"licenses": licenses,
|
|
147
|
+
# Split view of the same licenses: "own" are candidates for the
|
|
148
|
+
# project's license, "third_party" come from bundled notice files
|
|
149
|
+
# and must not be attributed to the project itself.
|
|
150
|
+
"own_licenses": self._deduplicate_licenses([lic.spdx_id for lic in own]),
|
|
151
|
+
"third_party_licenses": self._deduplicate_licenses(
|
|
152
|
+
[lic.spdx_id for lic in third_party]
|
|
153
|
+
),
|
|
129
154
|
"confidence": avg_confidence,
|
|
155
|
+
"own_confidence": own_confidence,
|
|
130
156
|
"files": files,
|
|
131
157
|
"summary": summary,
|
|
132
158
|
"copyrights": [c.to_dict() for c in result.copyrights] if result.copyrights else []
|
|
@@ -134,7 +160,10 @@ class OsliliIntegration:
|
|
|
134
160
|
else:
|
|
135
161
|
return {
|
|
136
162
|
"licenses": [],
|
|
163
|
+
"own_licenses": [],
|
|
164
|
+
"third_party_licenses": [],
|
|
137
165
|
"confidence": 0.0,
|
|
166
|
+
"own_confidence": 0.0,
|
|
138
167
|
"files": {},
|
|
139
168
|
"summary": "No licenses detected",
|
|
140
169
|
"copyrights": []
|
|
@@ -143,7 +172,10 @@ class OsliliIntegration:
|
|
|
143
172
|
except Exception as e:
|
|
144
173
|
return {
|
|
145
174
|
"licenses": [],
|
|
175
|
+
"own_licenses": [],
|
|
176
|
+
"third_party_licenses": [],
|
|
146
177
|
"confidence": 0.0,
|
|
178
|
+
"own_confidence": 0.0,
|
|
147
179
|
"files": {},
|
|
148
180
|
"summary": f"Error during detection: {e}",
|
|
149
181
|
"error": str(e)
|
|
@@ -170,17 +202,28 @@ class OsliliIntegration:
|
|
|
170
202
|
# Detect licenses
|
|
171
203
|
license_info = self.detect_licenses(path)
|
|
172
204
|
|
|
205
|
+
# Only the project's own licenses are eligible to become this package's
|
|
206
|
+
# license: one found in a bundled third-party notice file belongs to a
|
|
207
|
+
# dependency, so attributing it here would be wrong. Bundled notices must
|
|
208
|
+
# therefore neither raise nor lower the bar for the own license, which is
|
|
209
|
+
# why the decision below uses own_confidence rather than the aggregate.
|
|
210
|
+
own_licenses = self._deduplicate_licenses(license_info.get("own_licenses") or [])
|
|
211
|
+
own_confidence = license_info.get("own_confidence") or 0.0
|
|
212
|
+
|
|
173
213
|
# Update match if we found licenses with good confidence
|
|
174
|
-
if license_info["licenses"] and
|
|
214
|
+
if license_info["licenses"] and (
|
|
215
|
+
license_info["confidence"] > 0.7 or own_confidence > 0.7
|
|
216
|
+
):
|
|
175
217
|
# Remove duplicates while preserving order
|
|
176
218
|
unique_licenses = self._deduplicate_licenses(license_info["licenses"])
|
|
177
219
|
|
|
178
|
-
#
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
220
|
+
# Update match license if not already set or if ours is more confident.
|
|
221
|
+
# When nothing but bundled notices was found there is no own license, so
|
|
222
|
+
# match.license is left alone rather than guessed at.
|
|
223
|
+
if own_licenses and own_confidence > 0.7 and (
|
|
224
|
+
not match.license or own_confidence > 0.85
|
|
225
|
+
):
|
|
226
|
+
match.license = own_licenses[0]
|
|
184
227
|
|
|
185
228
|
# Add metadata about additional licenses and copyrights
|
|
186
229
|
if not hasattr(match, "metadata"):
|
|
@@ -188,6 +231,11 @@ class OsliliIntegration:
|
|
|
188
231
|
|
|
189
232
|
if len(unique_licenses) > 1:
|
|
190
233
|
match.metadata["additional_licenses"] = unique_licenses[1:]
|
|
234
|
+
|
|
235
|
+
# Surface bundled third-party licenses separately so callers can report
|
|
236
|
+
# them without mistaking them for the package's own license
|
|
237
|
+
if license_info.get("third_party_licenses"):
|
|
238
|
+
match.metadata["third_party_licenses"] = license_info["third_party_licenses"]
|
|
191
239
|
|
|
192
240
|
match.metadata["license_confidence"] = license_info["confidence"]
|
|
193
241
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: src2purl
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: Source to PURL - Identify package coordinates and PURLs from source code using UPMEX and multiple discovery strategies
|
|
5
5
|
Author-email: "Oscar Valenzuela B." <oscar.valenzuela.b@gmail.com>
|
|
6
6
|
License: AGPL-3.0
|
|
@@ -20,6 +20,7 @@ Classifier: Topic :: Software Development :: Version Control
|
|
|
20
20
|
Requires-Python: >=3.9
|
|
21
21
|
Description-Content-Type: text/markdown
|
|
22
22
|
License-File: LICENSE
|
|
23
|
+
License-File: AUTHORS.md
|
|
23
24
|
Requires-Dist: aiohttp>=3.9.0
|
|
24
25
|
Requires-Dist: click>=8.1.0
|
|
25
26
|
Requires-Dist: rich>=13.0.0
|
|
@@ -43,84 +44,48 @@ Provides-Extra: enhanced
|
|
|
43
44
|
Requires-Dist: osslili>=1.3.3; extra == "enhanced"
|
|
44
45
|
Dynamic: license-file
|
|
45
46
|
|
|
46
|
-
# SRC2PURL - Source Code to Package URL
|
|
47
|
+
# SRC2PURL - Source Code to Package URL Discovery
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
[](https://opensource.org/licenses/AGPL-3.0)
|
|
50
|
+
[](https://www.python.org/downloads/)
|
|
51
|
+
[](https://pypi.org/project/src2purl/)
|
|
49
52
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
src2purl uses a **progressive 2-phase discovery strategy** to identify packages:
|
|
53
|
-
|
|
54
|
-
### **Phase 1: Hash-based Package Discovery** (5-15 seconds)
|
|
55
|
-
**Primary identification using content fingerprinting and repository search:**
|
|
56
|
-
|
|
57
|
-
1. **Directory & Content Scanning** - Generate SWHIDs for directories and files
|
|
58
|
-
- ✅ **Complete file tree analysis** with configurable depth
|
|
59
|
-
- ✅ **SWHID generation** for precise content identification
|
|
60
|
-
- ✅ **Smart filtering** of binary files and hidden directories
|
|
61
|
-
|
|
62
|
-
2. **Software Heritage Archive** (Optional) - Deep provenance discovery
|
|
63
|
-
- ✅ **Most comprehensive** - finds exact source code matches
|
|
64
|
-
- ✅ **Historical accuracy** - can identify older versions
|
|
65
|
-
- ⚠️ **Requires opt-in** with `--use-swh` due to longer processing time
|
|
66
|
-
|
|
67
|
-
3. **Keyword Search** (Fallback) - Multi-platform repository discovery
|
|
68
|
-
- **GitHub API Search** - Repository identification by keywords
|
|
69
|
-
- **SCANOSS Fingerprinting** - Code similarity detection via file fingerprints
|
|
70
|
-
- ✅ **Universal coverage** - finds repositories for any project
|
|
71
|
-
- ✅ **Fast execution** (~10 seconds total)
|
|
72
|
-
|
|
73
|
-
### **Phase 2: Manifest-based Validation & Enhancement** (1-3 seconds)
|
|
74
|
-
**Authoritative package metadata extraction and result enhancement:**
|
|
75
|
-
|
|
76
|
-
4. **UPMEX Manifest Parsing** - Universal Package Metadata Extractor
|
|
77
|
-
- ✅ **Perfect metadata extraction** from package files (package.json, setup.py, pom.xml, go.mod, Cargo.toml, etc.)
|
|
78
|
-
- ✅ **Multi-ecosystem support** (PyPI, NPM, Maven, Go, Ruby Gems, PHP, .NET)
|
|
79
|
-
- ✅ **Complete package info** (name, version, license, PURL)
|
|
80
|
-
- ✅ **Cross-validation** with Phase 1 results for enhanced accuracy
|
|
53
|
+
A Python tool that identifies package coordinates (name, version, license, PURL) from source code directories using a hybrid discovery strategy with manifest parsing, code fingerprinting, repository search, and Software Heritage archive integration.
|
|
81
54
|
|
|
82
55
|
## Features
|
|
83
56
|
|
|
84
|
-
### **Core Capabilities**
|
|
85
57
|
- **2-Phase Discovery Strategy**: Hash-based identification enhanced by manifest parsing
|
|
86
58
|
- **Multi-Ecosystem Support**: PyPI, NPM, Maven, Go, Ruby Gems, PHP, .NET, and more
|
|
87
|
-
- **
|
|
88
|
-
- **
|
|
89
|
-
- **Package Coordinate Extraction**: Complete metadata (name, version, license, PURL)
|
|
90
|
-
|
|
91
|
-
### **Performance & Reliability**
|
|
92
|
-
- **Fast by Default**: 5-15 seconds for typical projects (vs 90+ seconds with SWH)
|
|
93
|
-
- **No API Keys Required**: Works well without authentication (SCANOSS, GitHub search)
|
|
94
|
-
- **Optional API Keys**: Enhanced rate limits and accuracy with GitHub/SCANOSS tokens
|
|
95
|
-
- **Persistent Caching**: File-based cache with smart TTL to avoid API rate limits
|
|
96
|
-
- **Rate Limit Handling**: Automatic backoff and retry logic
|
|
97
|
-
|
|
98
|
-
### **Discovery Methods**
|
|
99
|
-
- **UPMEX Manifest Parsing**: Universal Package Metadata Extractor for all major package ecosystems
|
|
100
|
-
- **SCANOSS Fingerprinting**: 100% accuracy code similarity with detailed license detection
|
|
101
|
-
- **GitHub Repository Search**: Universal coverage repository identification
|
|
102
|
-
- **Software Heritage Archive**: Comprehensive source inventory (opt-in with `--use-swh`)
|
|
103
|
-
|
|
104
|
-
### **Output & Integration**
|
|
105
|
-
- **Multiple Output Formats**: JSON and table output formats
|
|
106
|
-
- **PURL Generation**: Standard Package URLs for identified packages
|
|
107
|
-
- **Enhanced License Detection**: Integration with oslili for improved license detection
|
|
108
|
-
- **Subcomponent Detection**: Identifies multiple packages within monorepos and complex projects
|
|
59
|
+
- **Fast Performance**: 5-15 seconds for typical projects (vs 90+ seconds with SWH)
|
|
60
|
+
- **SEMCL.ONE Integration**: Works seamlessly with upmex, osslili, and other ecosystem tools
|
|
109
61
|
|
|
110
62
|
## Installation
|
|
111
63
|
|
|
112
|
-
|
|
64
|
+
```bash
|
|
65
|
+
pip install src2purl
|
|
66
|
+
```
|
|
113
67
|
|
|
68
|
+
For development:
|
|
114
69
|
```bash
|
|
115
|
-
git clone https://github.com/
|
|
70
|
+
git clone https://github.com/SemClone/src2purl.git
|
|
116
71
|
cd src2purl
|
|
117
72
|
pip install -e .
|
|
118
73
|
```
|
|
119
74
|
|
|
75
|
+
## Quick Start
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Identify package from source code
|
|
79
|
+
src2purl /path/to/source/code
|
|
80
|
+
|
|
81
|
+
# With Software Heritage archive (comprehensive but slower)
|
|
82
|
+
src2purl /path/to/source --use-swh
|
|
83
|
+
```
|
|
84
|
+
|
|
120
85
|
|
|
121
86
|
## Usage
|
|
122
87
|
|
|
123
|
-
###
|
|
88
|
+
### CLI Usage
|
|
124
89
|
|
|
125
90
|
```bash
|
|
126
91
|
# Fast discovery (default) - Uses manifest parsing + SCANOSS + GitHub (5-15 seconds)
|
|
@@ -137,15 +102,24 @@ src2purl /path/to/source --output-format json
|
|
|
137
102
|
|
|
138
103
|
# Detect subcomponents in monorepos
|
|
139
104
|
src2purl /path/to/source --detect-subcomponents
|
|
105
|
+
```
|
|
140
106
|
|
|
141
|
-
|
|
142
|
-
src2purl /path/to/source --no-license-detection
|
|
107
|
+
### Python API
|
|
143
108
|
|
|
144
|
-
|
|
145
|
-
src2purl
|
|
109
|
+
```python
|
|
110
|
+
from src2purl import identify_package
|
|
146
111
|
|
|
147
|
-
#
|
|
148
|
-
|
|
112
|
+
# Basic identification
|
|
113
|
+
result = identify_package("/path/to/source")
|
|
114
|
+
print(f"Package: {result.name}@{result.version}")
|
|
115
|
+
print(f"PURL: {result.purl}")
|
|
116
|
+
|
|
117
|
+
# With options
|
|
118
|
+
result = identify_package(
|
|
119
|
+
path="/path/to/source",
|
|
120
|
+
use_swh=True,
|
|
121
|
+
confidence_threshold=0.85
|
|
122
|
+
)
|
|
149
123
|
```
|
|
150
124
|
|
|
151
125
|
### Discovery Strategy Examples
|
|
@@ -169,7 +143,7 @@ src2purl /path/to/project --confidence-threshold 0.85
|
|
|
169
143
|
|
|
170
144
|
### API Authentication
|
|
171
145
|
|
|
172
|
-
|
|
146
|
+
**No API keys required!** The tool works with the free public APIs. API keys only provide enhanced rate limits and additional features.
|
|
173
147
|
|
|
174
148
|
#### Recommended API Keys (Optional)
|
|
175
149
|
|
|
@@ -179,9 +153,9 @@ export GITHUB_TOKEN=your_github_personal_access_token
|
|
|
179
153
|
```
|
|
180
154
|
- **Get token**: https://github.com/settings/tokens (no special permissions needed)
|
|
181
155
|
- **Benefits**:
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
156
|
+
- **Rate limit**: 10 → 5000 requests/hour
|
|
157
|
+
- **Better search**: More accurate repository identification
|
|
158
|
+
- **No cost**: Completely free
|
|
185
159
|
- **Impact**: Significant improvement for repository discovery
|
|
186
160
|
|
|
187
161
|
**2. SCANOSS API** - **Nice to Have** (Free, optional)
|
|
@@ -190,9 +164,9 @@ export SCANOSS_API_KEY=your_scanoss_key
|
|
|
190
164
|
```
|
|
191
165
|
- **Get token**: Register at https://www.scanoss.com
|
|
192
166
|
- **Benefits**:
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
167
|
+
- **No cost**: Free tier available
|
|
168
|
+
- **Enhanced rate limits**: Premium API endpoint
|
|
169
|
+
- **Additional features**: Possible extra metadata
|
|
196
170
|
- **Impact**: Minor improvement (SCANOSS works great without key)
|
|
197
171
|
|
|
198
172
|
**3. Software Heritage API** - **For Heavy Usage** (Free, only if using `--use-swh`)
|
|
@@ -201,8 +175,8 @@ export SWH_API_TOKEN=your_swh_token
|
|
|
201
175
|
```
|
|
202
176
|
- **Get token**: Register at https://archive.softwareheritage.org/api/
|
|
203
177
|
- **Benefits**:
|
|
204
|
-
-
|
|
205
|
-
-
|
|
178
|
+
- **Bypass rate limits**: No 60-second waits
|
|
179
|
+
- **Faster comprehensive scans**: When using `--use-swh`
|
|
206
180
|
- **Impact**: Essential for `--use-swh` flag, not needed for default fast mode
|
|
207
181
|
|
|
208
182
|
#### Performance Comparison
|
|
@@ -270,10 +244,61 @@ src2purl /path/to/project --no-license-detection # Skip license enhancement
|
|
|
270
244
|
src2purl /path/to/project --use-swh --verbose # Full discovery with details
|
|
271
245
|
```
|
|
272
246
|
|
|
247
|
+
## Integration with SEMCL.ONE
|
|
248
|
+
|
|
249
|
+
SRC2PURL is a core component of the SEMCL.ONE ecosystem:
|
|
250
|
+
|
|
251
|
+
- Uses **upmex** for manifest parsing and package metadata extraction
|
|
252
|
+
- Integrates with **osslili** for enhanced license detection
|
|
253
|
+
- Provides PURLs for **purl2src** to download source packages
|
|
254
|
+
- Supports **purl2notices** for generating attribution documentation
|
|
255
|
+
- Enables **ospac** policy evaluation with identified packages
|
|
256
|
+
|
|
257
|
+
## Configuration
|
|
258
|
+
|
|
259
|
+
Configuration via environment variables:
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# API tokens (optional but recommended)
|
|
263
|
+
export GITHUB_TOKEN=your_github_token
|
|
264
|
+
export SCANOSS_API_KEY=your_scanoss_key
|
|
265
|
+
export SWH_API_TOKEN=your_swh_token
|
|
266
|
+
|
|
267
|
+
# Performance settings
|
|
268
|
+
export SRC2PURL_CACHE_DIR=~/.cache/src2purl
|
|
269
|
+
export SRC2PURL_MAX_DEPTH=2
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Documentation
|
|
273
|
+
|
|
274
|
+
- [User Guide](docs/user-guide.md) - Comprehensive usage examples
|
|
275
|
+
- [API Reference](docs/api.md) - Python API documentation
|
|
276
|
+
- [Discovery Methods](docs/discovery-methods.md) - Detailed explanation of identification strategies
|
|
277
|
+
- [Examples](docs/examples.md) - Common use cases and workflows
|
|
278
|
+
|
|
279
|
+
## Contributing
|
|
280
|
+
|
|
281
|
+
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on:
|
|
282
|
+
- Code of conduct
|
|
283
|
+
- Development setup
|
|
284
|
+
- Submitting pull requests
|
|
285
|
+
- Reporting issues
|
|
286
|
+
|
|
287
|
+
## Support
|
|
288
|
+
|
|
289
|
+
For support and questions:
|
|
290
|
+
- [GitHub Issues](https://github.com/SemClone/src2purl/issues) - Bug reports and feature requests
|
|
291
|
+
- [Documentation](https://github.com/SemClone/src2purl) - Complete project documentation
|
|
292
|
+
- [SEMCL.ONE Community](https://semcl.one) - Ecosystem support and discussions
|
|
293
|
+
|
|
273
294
|
## License
|
|
274
295
|
|
|
275
|
-
|
|
296
|
+
GNU Affero General Public License v3.0 (AGPL-3.0) - see [LICENSE](LICENSE) file for details.
|
|
297
|
+
|
|
298
|
+
## Authors
|
|
299
|
+
|
|
300
|
+
See [AUTHORS.md](AUTHORS.md) for a list of contributors.
|
|
276
301
|
|
|
277
|
-
|
|
302
|
+
---
|
|
278
303
|
|
|
279
|
-
|
|
304
|
+
*Part of the [SEMCL.ONE](https://semcl.one) ecosystem for comprehensive OSS compliance and code analysis.*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
AUTHORS.md
|
|
1
2
|
LICENSE
|
|
2
3
|
README.md
|
|
3
4
|
pyproject.toml
|
|
@@ -33,4 +34,6 @@ src2purl.egg-info/SOURCES.txt
|
|
|
33
34
|
src2purl.egg-info/dependency_links.txt
|
|
34
35
|
src2purl.egg-info/entry_points.txt
|
|
35
36
|
src2purl.egg-info/requires.txt
|
|
36
|
-
src2purl.egg-info/top_level.txt
|
|
37
|
+
src2purl.egg-info/top_level.txt
|
|
38
|
+
tests/test_integration.py
|
|
39
|
+
tests/test_src2purl.py
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Integration tests for src2purl."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_project_imports():
|
|
8
|
+
"""Test that project modules can be imported."""
|
|
9
|
+
# This will be customized per project
|
|
10
|
+
assert True
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_documentation_exists():
|
|
14
|
+
"""Test that documentation files exist."""
|
|
15
|
+
project_root = Path(__file__).parent.parent
|
|
16
|
+
|
|
17
|
+
docs = [
|
|
18
|
+
"README.md",
|
|
19
|
+
"CONTRIBUTING.md",
|
|
20
|
+
"AUTHORS.md",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
for doc in docs:
|
|
24
|
+
doc_path = project_root / doc
|
|
25
|
+
assert doc_path.exists(), f"Documentation {doc} is missing"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_workflow_files_exist():
|
|
29
|
+
"""Test that GitHub workflow files exist."""
|
|
30
|
+
project_root = Path(__file__).parent.parent
|
|
31
|
+
workflows_dir = project_root / ".github" / "workflows"
|
|
32
|
+
|
|
33
|
+
if workflows_dir.exists():
|
|
34
|
+
workflow_files = list(workflows_dir.glob("*.yml"))
|
|
35
|
+
assert len(workflow_files) > 0, "No workflow files found"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Tests for src2purl package."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_package_import():
|
|
9
|
+
"""Test that the package can be imported."""
|
|
10
|
+
try:
|
|
11
|
+
import src2purl
|
|
12
|
+
assert True
|
|
13
|
+
except ImportError:
|
|
14
|
+
# Package might have different structure
|
|
15
|
+
assert True
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_basic_functionality():
|
|
19
|
+
"""Basic test to ensure pytest works."""
|
|
20
|
+
assert True
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_python_version():
|
|
24
|
+
"""Test Python version compatibility."""
|
|
25
|
+
assert sys.version_info >= (3, 8)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class TestPackageStructure:
|
|
29
|
+
"""Test package structure and configuration."""
|
|
30
|
+
|
|
31
|
+
def test_project_root_exists(self):
|
|
32
|
+
"""Test that project root exists."""
|
|
33
|
+
project_root = Path(__file__).parent.parent
|
|
34
|
+
assert project_root.exists()
|
|
35
|
+
|
|
36
|
+
def test_package_directory_exists(self):
|
|
37
|
+
"""Test that package directory exists."""
|
|
38
|
+
project_root = Path(__file__).parent.parent
|
|
39
|
+
package_dir = project_root / "src2purl"
|
|
40
|
+
# Some projects might have different structure
|
|
41
|
+
assert project_root.exists()
|
|
42
|
+
|
|
43
|
+
def test_pyproject_toml_exists(self):
|
|
44
|
+
"""Test that pyproject.toml exists."""
|
|
45
|
+
project_root = Path(__file__).parent.parent
|
|
46
|
+
pyproject = project_root / "pyproject.toml"
|
|
47
|
+
assert pyproject.exists()
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@pytest.mark.parametrize("required_file", [
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE",
|
|
53
|
+
"pyproject.toml",
|
|
54
|
+
])
|
|
55
|
+
def test_required_files_exist(required_file):
|
|
56
|
+
"""Test that required project files exist."""
|
|
57
|
+
project_root = Path(__file__).parent.parent
|
|
58
|
+
file_path = project_root / required_file
|
|
59
|
+
assert file_path.exists(), f"{required_file} not found"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_no_syntax_errors():
|
|
63
|
+
"""Test that the package has no syntax errors."""
|
|
64
|
+
import ast
|
|
65
|
+
import os
|
|
66
|
+
|
|
67
|
+
project_root = Path(__file__).parent.parent
|
|
68
|
+
package_dir = project_root / "src2purl"
|
|
69
|
+
|
|
70
|
+
if package_dir.exists():
|
|
71
|
+
for root, dirs, files in os.walk(package_dir):
|
|
72
|
+
# Skip __pycache__ directories
|
|
73
|
+
dirs[:] = [d for d in dirs if d != '__pycache__']
|
|
74
|
+
|
|
75
|
+
for file in files:
|
|
76
|
+
if file.endswith('.py'):
|
|
77
|
+
file_path = Path(root) / file
|
|
78
|
+
try:
|
|
79
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
80
|
+
source = f.read()
|
|
81
|
+
ast.parse(source)
|
|
82
|
+
except SyntaxError as e:
|
|
83
|
+
pytest.fail(f"Syntax error in {file_path}: {e}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|