cpyte-cpm 1.1.1__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.
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: cpyte-cpm
3
+ Version: 1.1.1
4
+ Summary: A package manager for cpy
5
+ Author: Albert
6
+ License: MIT
7
+ Project-URL: Homepage, https://gitea.5gnew.io.vn/Cpyte-Project/Cpyte
8
+ Requires-Python: >=3.11
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.34.2
11
+ Requires-Dist: cpyte
12
+ Requires-Dist: packaging
13
+ Provides-Extra: dev
14
+ Requires-Dist: pytest>=9.1.1; extra == "dev"
15
+
16
+ # CPM — Cpyte Package Manager
17
+
18
+ A compiler-pipeline dependency resolver and installer for the [Cpyte](https://gitea.5gnew.io.vn/Cpyte-Project/Cpyte) programming language.
19
+
20
+ ```
21
+ import @std.json
22
+
23
+ Resolve → Lower → Optimize → Execute
24
+ ```
25
+
26
+ ## Install
27
+
28
+ ```bash
29
+ pip install cpm
30
+ ```
31
+
32
+ ## Quick Start
33
+
34
+ ```bash
35
+ cpm init # create cpytoml
36
+ cpm add @std/json@^2.0 # add a dependency
37
+ cpm install # install all from manifest
38
+ ```
39
+
40
+ ## Commands
41
+
42
+ | Command | Description |
43
+ |---------|-------------|
44
+ | `cpm init` | Initialize a new project (`cpytoml`) |
45
+ | `cpm add <pkg>` | Add packages to manifest + install |
46
+ | `cpm remove <pkg>` | Remove packages + uninstall |
47
+ | `cpm install` | Install all dependencies from manifest |
48
+ | `cpm install <pkg>` | Install specific packages |
49
+ | `cpm update` | Re-resolve and update all packages |
50
+ | `cpm update <pkg>` | Re-resolve specific packages |
51
+ | `cpm build` | Build the project |
52
+ | `cpm run <script>` | Run a named script |
53
+
54
+ ## Global Flags
55
+
56
+ ```
57
+ -v, --verbose Verbose output
58
+ -q, --quiet Suppress output
59
+ -y, --yes Auto-confirm prompts
60
+ --offline Offline mode (skip network)
61
+ --no-cache Disable cache
62
+ --config <path> Custom config / repo URL
63
+ --target <platform> Target platform (e.g. linux/x86_64)
64
+ --llvm-version <V> LLVM version for prebuilt packages
65
+ ```
66
+
67
+ ## Manifest (`cpytoml`)
68
+
69
+ ```toml
70
+ [cpm]
71
+ name = "my-app"
72
+ version = "1.0"
73
+ prebuilt = false
74
+ llvm_version = "18.1.0"
75
+
76
+ [cpm.target]
77
+ os = "linux"
78
+ arch = "x86_64"
79
+ features = ["gui", "ssl"]
80
+
81
+ [cpm.dependencies]
82
+ "@std/json" = "^2.0"
83
+ "@std/http" = "1.0"
84
+ "package_a" = "latest"
85
+ ```
86
+
87
+ ## Claims System
88
+
89
+ Packages declare platform support; CPM filters based on your target.
90
+
91
+ **Package metadata (registry):**
92
+ ```json
93
+ {
94
+ "name": "win32-api",
95
+ "claims": { "os": ["windows"], "arch": ["x86_64"] }
96
+ }
97
+ ```
98
+
99
+ **Resolution:**
100
+ | Target | `win32-api` (windows) | `posix-api` (linux) | `@std/json` (any) |
101
+ |--------|----------------------|--------------------|--------------------|
102
+ | `linux/x86_64` | skip | install | install |
103
+ | `windows/x86_64` | install | skip | install |
104
+
105
+ Auto-detects from current platform when no target is specified.
106
+
107
+ ## Prebuilt Mode
108
+
109
+ When the registry serves precompiled LLVM IR:
110
+
111
+ ```toml
112
+ [cpm]
113
+ prebuilt = true
114
+ llvm_version = "18.1.0"
115
+ ```
116
+
117
+ CPM fetches `.ll` artifacts from `metadata/prebuilt/...` and skips source packages
118
+ with incompatible LLVM versions (major version must match).
119
+
120
+ ## Lockfile (`cpm.lock`)
121
+
122
+ Auto-generated. Pins exact versions for reproducible builds.
123
+
124
+ ```toml
125
+ [[package]]
126
+ name = "@std/json"
127
+ version = "2.0.3"
128
+ resolved = "https://repo.example.com/group/std/json/2.0.3.tar.gz"
129
+ checksum = "sha256:abc123..."
130
+ dependencies = ["@std/encoding@1.2.0"]
131
+ llvm_version = "18.1.0"
132
+ cpyte_version = "0.5.0"
133
+ ```
134
+
135
+ ## Directory Layout
136
+
137
+ ```
138
+ ~/.cpm/
139
+ ├── cache/<name>/<version>/ # downloaded archives
140
+ └── modules/<name>/<version>/ # extracted packages
141
+ ```
142
+
143
+ ## License
144
+
145
+ MIT
@@ -0,0 +1,130 @@
1
+ # CPM — Cpyte Package Manager
2
+
3
+ A compiler-pipeline dependency resolver and installer for the [Cpyte](https://gitea.5gnew.io.vn/Cpyte-Project/Cpyte) programming language.
4
+
5
+ ```
6
+ import @std.json
7
+
8
+ Resolve → Lower → Optimize → Execute
9
+ ```
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install cpm
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```bash
20
+ cpm init # create cpytoml
21
+ cpm add @std/json@^2.0 # add a dependency
22
+ cpm install # install all from manifest
23
+ ```
24
+
25
+ ## Commands
26
+
27
+ | Command | Description |
28
+ |---------|-------------|
29
+ | `cpm init` | Initialize a new project (`cpytoml`) |
30
+ | `cpm add <pkg>` | Add packages to manifest + install |
31
+ | `cpm remove <pkg>` | Remove packages + uninstall |
32
+ | `cpm install` | Install all dependencies from manifest |
33
+ | `cpm install <pkg>` | Install specific packages |
34
+ | `cpm update` | Re-resolve and update all packages |
35
+ | `cpm update <pkg>` | Re-resolve specific packages |
36
+ | `cpm build` | Build the project |
37
+ | `cpm run <script>` | Run a named script |
38
+
39
+ ## Global Flags
40
+
41
+ ```
42
+ -v, --verbose Verbose output
43
+ -q, --quiet Suppress output
44
+ -y, --yes Auto-confirm prompts
45
+ --offline Offline mode (skip network)
46
+ --no-cache Disable cache
47
+ --config <path> Custom config / repo URL
48
+ --target <platform> Target platform (e.g. linux/x86_64)
49
+ --llvm-version <V> LLVM version for prebuilt packages
50
+ ```
51
+
52
+ ## Manifest (`cpytoml`)
53
+
54
+ ```toml
55
+ [cpm]
56
+ name = "my-app"
57
+ version = "1.0"
58
+ prebuilt = false
59
+ llvm_version = "18.1.0"
60
+
61
+ [cpm.target]
62
+ os = "linux"
63
+ arch = "x86_64"
64
+ features = ["gui", "ssl"]
65
+
66
+ [cpm.dependencies]
67
+ "@std/json" = "^2.0"
68
+ "@std/http" = "1.0"
69
+ "package_a" = "latest"
70
+ ```
71
+
72
+ ## Claims System
73
+
74
+ Packages declare platform support; CPM filters based on your target.
75
+
76
+ **Package metadata (registry):**
77
+ ```json
78
+ {
79
+ "name": "win32-api",
80
+ "claims": { "os": ["windows"], "arch": ["x86_64"] }
81
+ }
82
+ ```
83
+
84
+ **Resolution:**
85
+ | Target | `win32-api` (windows) | `posix-api` (linux) | `@std/json` (any) |
86
+ |--------|----------------------|--------------------|--------------------|
87
+ | `linux/x86_64` | skip | install | install |
88
+ | `windows/x86_64` | install | skip | install |
89
+
90
+ Auto-detects from current platform when no target is specified.
91
+
92
+ ## Prebuilt Mode
93
+
94
+ When the registry serves precompiled LLVM IR:
95
+
96
+ ```toml
97
+ [cpm]
98
+ prebuilt = true
99
+ llvm_version = "18.1.0"
100
+ ```
101
+
102
+ CPM fetches `.ll` artifacts from `metadata/prebuilt/...` and skips source packages
103
+ with incompatible LLVM versions (major version must match).
104
+
105
+ ## Lockfile (`cpm.lock`)
106
+
107
+ Auto-generated. Pins exact versions for reproducible builds.
108
+
109
+ ```toml
110
+ [[package]]
111
+ name = "@std/json"
112
+ version = "2.0.3"
113
+ resolved = "https://repo.example.com/group/std/json/2.0.3.tar.gz"
114
+ checksum = "sha256:abc123..."
115
+ dependencies = ["@std/encoding@1.2.0"]
116
+ llvm_version = "18.1.0"
117
+ cpyte_version = "0.5.0"
118
+ ```
119
+
120
+ ## Directory Layout
121
+
122
+ ```
123
+ ~/.cpm/
124
+ ├── cache/<name>/<version>/ # downloaded archives
125
+ └── modules/<name>/<version>/ # extracted packages
126
+ ```
127
+
128
+ ## License
129
+
130
+ MIT
@@ -0,0 +1 @@
1
+ """CPM - A package manager for Cpyte."""
File without changes
@@ -0,0 +1,108 @@
1
+ """Structured types for parsed CLI commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import List, Optional, Union
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class GlobalOptions:
11
+ verbose: bool = False
12
+ quiet: bool = False
13
+ yes: bool = False
14
+ offline: bool = False
15
+ no_cache: bool = False
16
+ config: Optional[str] = None
17
+ server: List[str] = field(default_factory=list)
18
+ target: Optional[str] = None
19
+ llvm_version: Optional[str] = None
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class InitCommand:
24
+ pass
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class AddCommand:
29
+ packages: List[str] = field(default_factory=list)
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class RemoveCommand:
34
+ packages: List[str] = field(default_factory=list)
35
+
36
+
37
+ @dataclass(frozen=True)
38
+ class InstallCommand:
39
+ packages: List[str] = field(default_factory=list)
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class UpdateCommand:
44
+ packages: List[str] = field(default_factory=list)
45
+
46
+
47
+ @dataclass(frozen=True)
48
+ class BuildCommand:
49
+ pass
50
+
51
+
52
+ @dataclass(frozen=True)
53
+ class RunCommand:
54
+ script: str = ""
55
+ args: List[str] = field(default_factory=list)
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class VersionCommand:
60
+ pass
61
+
62
+
63
+ @dataclass(frozen=True)
64
+ class PublishCommand:
65
+ directory: str = ""
66
+ name: str = ""
67
+ version: str = ""
68
+ requires: List[str] = field(default_factory=list)
69
+ prebuilt: bool = False
70
+ llvm_version: str = ""
71
+ cpyte_version: str = ""
72
+ server: str = ""
73
+ token: str = ""
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class UnpublishCommand:
78
+ name: str = ""
79
+ version: str = ""
80
+ all: bool = False
81
+ block: bool = False
82
+ server: str = ""
83
+
84
+
85
+ @dataclass(frozen=True)
86
+ class SearchCommand:
87
+ query: str = ""
88
+
89
+
90
+ Command = Union[
91
+ InitCommand,
92
+ AddCommand,
93
+ RemoveCommand,
94
+ InstallCommand,
95
+ UpdateCommand,
96
+ BuildCommand,
97
+ RunCommand,
98
+ VersionCommand,
99
+ PublishCommand,
100
+ UnpublishCommand,
101
+ SearchCommand,
102
+ ]
103
+
104
+
105
+ @dataclass(frozen=True)
106
+ class ParsedCLI:
107
+ global_options: GlobalOptions = field(default_factory=GlobalOptions)
108
+ command: Optional[Command] = None
@@ -0,0 +1,49 @@
1
+ """CLI error types for clear, user-facing error messages."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ class CLIError(Exception):
7
+ """Base error for CLI parsing issues."""
8
+
9
+
10
+ class UnknownCommandError(CLIError):
11
+ """Raised when an unrecognized subcommand is given."""
12
+
13
+ def __init__(self, command: str, suggestions: list[str] | None = None) -> None:
14
+ self.command = command
15
+ self.suggestions = suggestions or []
16
+ msg = f"unknown command '{command}'"
17
+ if self.suggestions:
18
+ msg += f". Did you mean: {', '.join(self.suggestions)}?"
19
+ super().__init__(msg)
20
+
21
+
22
+ class MissingArgumentError(CLIError):
23
+ """Raised when a required argument is missing."""
24
+
25
+ def __init__(self, argument: str, command: str) -> None:
26
+ self.argument = argument
27
+ self.command = command
28
+ super().__init__(
29
+ f"command '{command}' requires {argument}"
30
+ )
31
+
32
+
33
+ class InvalidPackageSpecError(CLIError):
34
+ """Raised when a package specification is invalid."""
35
+
36
+ def __init__(self, spec: str) -> None:
37
+ self.spec = spec
38
+ super().__init__(f"invalid package specification: '{spec}'")
39
+
40
+
41
+ class InvalidOptionError(CLIError):
42
+ """Raised when an invalid option is provided."""
43
+
44
+ def __init__(self, option: str, message: str = "") -> None:
45
+ self.option = option
46
+ msg = f"invalid option '{option}'"
47
+ if message:
48
+ msg += f": {message}"
49
+ super().__init__(msg)
@@ -0,0 +1,263 @@
1
+ """Execution engine for CPM instruction streams.
2
+
3
+ Pipeline stage: Execute
4
+ Consumes the flat instruction list produced by deduplicator() and
5
+ performs actual filesystem operations.
6
+
7
+ Directory layout:
8
+ ~/.cpm/cache/<name>/<version>/ — downloaded artifacts (shared cache)
9
+ <project>/.cpm/modules/<name>/<version>/ — installed packages (project-local)
10
+
11
+ Modes:
12
+ prebuilt = false (default): install source (.cpy files)
13
+ prebuilt = true: install precompiled LLVM IR (.ll)
14
+ registry serves prebuilt artifacts
15
+ """
16
+
17
+ import hashlib
18
+ import os
19
+ import shutil
20
+ import tarfile
21
+ import tempfile
22
+ import zipfile
23
+ from pathlib import Path
24
+
25
+ import requests as rq
26
+
27
+ from .gethins import fetch_repo
28
+
29
+
30
+ CPM_HOME = Path.home() / ".cpm"
31
+ CACHE_DIR = CPM_HOME / "cache"
32
+
33
+
34
+ def _ensure_cache_dir():
35
+ """Create CPM cache directory if it doesn't exist."""
36
+ CACHE_DIR.mkdir(parents=True, exist_ok=True)
37
+
38
+
39
+ def _cache_path(name: str, version: str) -> Path:
40
+ """Return the cache directory for a specific package version."""
41
+ return CACHE_DIR / name / version
42
+
43
+
44
+ def _module_path(project_root: Path, name: str, version: str) -> Path:
45
+ """Return the project-local install directory for a specific package version."""
46
+ return project_root / ".cpm" / "modules" / name / version
47
+
48
+
49
+ def calculate_checksum(file_path: str, algorithm: str = "sha256") -> str:
50
+ """Calculate checksum of a file."""
51
+ hash_func = hashlib.new(algorithm)
52
+ with open(file_path, "rb") as fp:
53
+ for chunk in iter(lambda: fp.read(4096), b""):
54
+ hash_func.update(chunk)
55
+ return f"{algorithm}:{hash_func.hexdigest()}"
56
+
57
+
58
+ def _verify_checksum(file_path: str, expected: str) -> None:
59
+ """Verify a file's checksum. Expected format: 'algorithm:hex'."""
60
+ if ":" not in expected:
61
+ raise ValueError(f"Checksum must be 'algorithm:hex', got: {expected}")
62
+ algorithm, expected_hex = expected.split(":", 1)
63
+ actual = calculate_checksum(file_path, algorithm)
64
+ actual_hex = actual.split(":", 1)[1]
65
+ if actual_hex != expected_hex:
66
+ os.remove(file_path)
67
+ raise ValueError(
68
+ f"Checksum mismatch for {file_path}: "
69
+ f"expected {expected}, got {actual}"
70
+ )
71
+
72
+
73
+ def _download(url: str, dest: Path) -> None:
74
+ """Download a file from url to dest."""
75
+ response = rq.get(url, stream=True, timeout=30)
76
+ response.raise_for_status()
77
+ with open(dest, "wb") as fp:
78
+ for chunk in response.iter_content(chunk_size=8192):
79
+ fp.write(chunk)
80
+
81
+
82
+ def _extract(archive_path: Path, dest: Path) -> None:
83
+ """Extract a tar.gz or zip archive into dest."""
84
+ dest.mkdir(parents=True, exist_ok=True)
85
+ name = archive_path.name
86
+ if name.endswith(".tar.gz") or name.endswith(".tgz"):
87
+ with tarfile.open(archive_path, "r:gz") as tar:
88
+ tar.extractall(dest)
89
+ elif name.endswith(".tar.bz2"):
90
+ with tarfile.open(archive_path, "r:bz2") as tar:
91
+ tar.extractall(dest)
92
+ elif name.endswith(".tar"):
93
+ with tarfile.open(archive_path, "r") as tar:
94
+ tar.extractall(dest)
95
+ elif name.endswith(".zip"):
96
+ with zipfile.ZipFile(archive_path, "r") as zf:
97
+ zf.extractall(dest)
98
+ else:
99
+ # Raw file (e.g. .ll, .bc, .cpy) — copy directly
100
+ dest.mkdir(parents=True, exist_ok=True)
101
+ shutil.copy2(archive_path, dest / name)
102
+
103
+
104
+ def _is_cached(name: str, version: str, checksum: str) -> bool:
105
+ """Check if a package is already in cache with matching checksum."""
106
+ cache = _cache_path(name, version)
107
+ if not cache.exists():
108
+ return False
109
+ for f in cache.iterdir():
110
+ if f.is_file():
111
+ try:
112
+ actual = calculate_checksum(str(f))
113
+ if actual == checksum:
114
+ return True
115
+ except Exception:
116
+ continue
117
+ return False
118
+
119
+
120
+ def _install_from_cache(project_root: Path, name: str, version: str) -> None:
121
+ """Copy a cached archive into the project modules directory and extract."""
122
+ cache = _cache_path(name, version)
123
+ target = _module_path(project_root, name, version)
124
+
125
+ if target.exists():
126
+ shutil.rmtree(target)
127
+
128
+ for f in cache.iterdir():
129
+ if f.is_file():
130
+ _extract(f, target)
131
+ return
132
+
133
+
134
+ def execute_get(inst: dict, project_root: Path, prebuilt: bool = False) -> None:
135
+ """Execute a single GET instruction.
136
+
137
+ Parameters
138
+ ----------
139
+ inst:
140
+ Instruction dict with GET, url, version, checksum.
141
+ project_root:
142
+ Project root directory (where .cpm/modules/ lives).
143
+ prebuilt:
144
+ If True, fetch prebuilt .ll from registry.
145
+ """
146
+ name = inst["GET"]
147
+ url = inst.get("url")
148
+ version = inst.get("version", "latest")
149
+ checksum = inst.get("checksum")
150
+
151
+ if not url:
152
+ raise ValueError(f"No URL provided for package '{name}'")
153
+
154
+ target = _module_path(project_root, name, version)
155
+
156
+ # Already installed — skip
157
+ if target.exists():
158
+ print(f" {name}@{version} already installed")
159
+ return
160
+
161
+ cache = _cache_path(name, version)
162
+
163
+ # Check cache
164
+ if checksum and _is_cached(name, version, checksum):
165
+ print(f" {name}@{version} found in cache, installing...")
166
+ _install_from_cache(project_root, name, version)
167
+ print(f" {name}@{version} installed")
168
+ return
169
+
170
+ # Download
171
+ print(f" downloading {name}@{version}...")
172
+ cache.mkdir(parents=True, exist_ok=True)
173
+ filename = url.rsplit("/", 1)[-1] or f"{name}-{version}.tar.gz"
174
+ dest = cache / filename
175
+ _download(url, dest)
176
+
177
+ # Verify checksum
178
+ if checksum:
179
+ print(f" verifying checksum...")
180
+ _verify_checksum(str(dest), checksum)
181
+
182
+ # Extract to cache first
183
+ print(f" installing {name}@{version}...")
184
+ _extract(dest, cache)
185
+
186
+ # Install from cache to project
187
+ target.parent.mkdir(parents=True, exist_ok=True)
188
+ shutil.copytree(cache, target)
189
+
190
+ mode = "prebuilt" if prebuilt else "source"
191
+ print(f" {name}@{version} installed [{mode}]")
192
+
193
+
194
+ def execute_remove(inst: dict, project_root: Path) -> None:
195
+ """Execute a single REMOVE instruction."""
196
+ name = inst["REMOVE"]
197
+ target = project_root / ".cpm" / "modules" / name
198
+
199
+ if not target.exists():
200
+ print(f" {name} not installed, skipping")
201
+ return
202
+
203
+ shutil.rmtree(target)
204
+ print(f" {name} removed")
205
+
206
+
207
+ def execute(
208
+ instructions: list[dict],
209
+ project_root: Path,
210
+ repo: str = None,
211
+ ver: str = "latest",
212
+ prebuilt: bool = False,
213
+ ) -> None:
214
+ """Execute a flat instruction stream.
215
+
216
+ Pipeline stage: Execute
217
+ This is the final stage — instructions are consumed and filesystem
218
+ operations are performed.
219
+
220
+ Parameters
221
+ ----------
222
+ instructions:
223
+ Flat list of dicts from deduplicator(), e.g.
224
+ [{"GET": "D", "url": "...", ...}, {"GET": "B", ...}]
225
+ project_root:
226
+ Project root directory (where .cpm/modules/ lives).
227
+ repo:
228
+ Repository URL. If provided, missing metadata (url, checksum)
229
+ will be fetched during execution.
230
+ ver:
231
+ Package version to resolve against.
232
+ prebuilt:
233
+ If True, registry serves prebuilt artifacts (.ll).
234
+ """
235
+ _ensure_cache_dir()
236
+
237
+ total = len(instructions)
238
+ mode = "prebuilt" if prebuilt else "source"
239
+ print(f"\nExecuting {total} instruction(s) [{mode}]...\n")
240
+
241
+ for i, inst in enumerate(instructions, 1):
242
+ # If metadata is missing from instruction, fetch it
243
+ if "GET" in inst and "url" not in inst:
244
+ name = inst["GET"]
245
+ if repo is None:
246
+ raise ValueError(
247
+ f"Instruction for '{name}' has no URL and no repo provided"
248
+ )
249
+ metadata = fetch_repo(repo, f"metadata/{name}/{ver}")
250
+ inst["url"] = metadata["url"]
251
+ inst["checksum"] = metadata.get("checksum", "")
252
+ inst["version"] = metadata.get("version", ver)
253
+
254
+ if "GET" in inst:
255
+ print(f"[{i}/{total}] GET {inst['GET']}")
256
+ execute_get(inst, project_root, prebuilt=prebuilt)
257
+ elif "REMOVE" in inst:
258
+ print(f"[{i}/{total}] REMOVE {inst['REMOVE']}")
259
+ execute_remove(inst, project_root)
260
+ else:
261
+ print(f"[{i}/{total}] SKIP unknown instruction: {inst}")
262
+
263
+ print(f"\nDone. {total} instruction(s) executed.")