primitive 0.1.14__tar.gz → 0.1.16__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.
Files changed (58) hide show
  1. {primitive-0.1.14 → primitive-0.1.16}/PKG-INFO +1 -1
  2. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/__about__.py +1 -1
  3. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/agent/actions.py +53 -22
  4. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/cli.py +2 -0
  5. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/client.py +2 -2
  6. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/daemons/launch_agents.py +11 -1
  7. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/daemons/launch_service.py +10 -1
  8. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/jobs/actions.py +4 -0
  9. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/lint/actions.py +1 -1
  10. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/projects/actions.py +0 -1
  11. primitive-0.1.16/src/primitive/sim/actions.py +190 -0
  12. primitive-0.1.16/src/primitive/sim/commands.py +19 -0
  13. primitive-0.1.16/src/primitive/sim/vcd.py +761 -0
  14. primitive-0.1.14/src/primitive/simulations/actions.py +0 -48
  15. {primitive-0.1.14 → primitive-0.1.16}/.git-hooks/pre-commit +0 -0
  16. {primitive-0.1.14 → primitive-0.1.16}/.gitattributes +0 -0
  17. {primitive-0.1.14 → primitive-0.1.16}/.github/workflows/lint.yml +0 -0
  18. {primitive-0.1.14 → primitive-0.1.16}/.github/workflows/publish.yml +0 -0
  19. {primitive-0.1.14 → primitive-0.1.16}/.gitignore +0 -0
  20. {primitive-0.1.14 → primitive-0.1.16}/.vscode/settings.json +0 -0
  21. {primitive-0.1.14 → primitive-0.1.16}/LICENSE.txt +0 -0
  22. {primitive-0.1.14 → primitive-0.1.16}/Makefile +0 -0
  23. {primitive-0.1.14 → primitive-0.1.16}/README.md +0 -0
  24. {primitive-0.1.14 → primitive-0.1.16}/linux setup.md +0 -0
  25. {primitive-0.1.14 → primitive-0.1.16}/pyproject.toml +0 -0
  26. {primitive-0.1.14 → primitive-0.1.16}/requirements.txt +0 -0
  27. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/__init__.py +0 -0
  28. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/agent/commands.py +0 -0
  29. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/auth/__init__.py +0 -0
  30. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/auth/actions.py +0 -0
  31. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/auth/commands.py +0 -0
  32. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/daemons/actions.py +0 -0
  33. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/daemons/commands.py +0 -0
  34. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/files/actions.py +0 -0
  35. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/files/commands.py +0 -0
  36. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/git/__init__.py +0 -0
  37. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/git/actions.py +0 -0
  38. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/git/commands.py +0 -0
  39. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/graphql/__init__.py +0 -0
  40. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/graphql/sdk.py +0 -0
  41. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/hardware/actions.py +0 -0
  42. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/hardware/commands.py +0 -0
  43. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/jobs/commands.py +0 -0
  44. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/lint/commands.py +0 -0
  45. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/organizations/actions.py +0 -0
  46. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/organizations/commands.py +0 -0
  47. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/projects/__init__.py +0 -0
  48. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/projects/commands.py +0 -0
  49. {primitive-0.1.14/src/primitive/simulations → primitive-0.1.16/src/primitive/sim}/__init__.py +0 -0
  50. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/actions.py +0 -0
  51. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/config.py +0 -0
  52. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/files.py +0 -0
  53. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/git.py +0 -0
  54. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/memory_size.py +0 -0
  55. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/printer.py +0 -0
  56. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/shell.py +0 -0
  57. {primitive-0.1.14 → primitive-0.1.16}/src/primitive/utils/verible.py +0 -0
  58. {primitive-0.1.14 → primitive-0.1.16}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: primitive
3
- Version: 0.1.14
3
+ Version: 0.1.16
4
4
  Project-URL: Documentation, https://github.com//primitivecorp/primitive-cli#readme
5
5
  Project-URL: Issues, https://github.com//primitivecorp/primitive-cli/issues
6
6
  Project-URL: Source, https://github.com//primitivecorp/primitive-cli
@@ -1,4 +1,4 @@
1
1
  # SPDX-FileCopyrightText: 2024-present Dylan Stein <dylan@steins.studio>
2
2
  #
3
3
  # SPDX-License-Identifier: MIT
4
- __version__ = "0.1.14"
4
+ __version__ = "0.1.16"
@@ -65,28 +65,59 @@ class Agent(BaseAction):
65
65
  )
66
66
  )
67
67
 
68
- if job_run["job"]["slug"] == "lint":
69
- logger.debug("Executing Lint Job")
70
-
71
- self.primitive.jobs.job_run_update(
72
- job_run["id"], status="request_in_progress"
73
- )
74
-
75
- result, message = self.primitive.lint.execute(
76
- source=downloaded_git_repository_dir
77
- )
78
- if result:
79
- conclusion = "success"
80
- else:
81
- conclusion = "failure"
82
- self.primitive.jobs.job_run_update(
83
- job_run["id"],
84
- status="request_completed",
85
- conclusion=conclusion,
86
- stdout=message,
87
- )
88
-
89
- logger.debug("Lint Job Completed")
68
+ match job_run["job"]["slug"]:
69
+ case "lint":
70
+ logger.debug("Executing Lint Job")
71
+
72
+ self.primitive.jobs.job_run_update(
73
+ job_run["id"], status="request_in_progress"
74
+ )
75
+
76
+ result, message = self.primitive.lint.execute(
77
+ source=downloaded_git_repository_dir
78
+ )
79
+ if result:
80
+ conclusion = "success"
81
+ else:
82
+ conclusion = "failure"
83
+ self.primitive.jobs.job_run_update(
84
+ job_run["id"],
85
+ status="request_completed",
86
+ conclusion=conclusion,
87
+ stdout=message,
88
+ )
89
+
90
+ logger.debug("Lint Job Completed")
91
+ case "sim":
92
+ logger.debug("Executing Sim Job")
93
+
94
+ self.primitive.job.job_run_update(
95
+ job_run["id"], status="request_in_progress"
96
+ )
97
+
98
+ result, message = self.primitive.sim.execute(
99
+ source=downloaded_git_repository_dir,
100
+ cmd=(
101
+ "make",
102
+ "all",
103
+ ), # TODO: Change this to use container args container cmd
104
+ )
105
+
106
+ # Attempt artifact collection
107
+ self.primitive.sim.collect_artifacts(
108
+ source=downloaded_git_repository_dir
109
+ )
110
+
111
+ if result:
112
+ conclusion = "success"
113
+ else:
114
+ conclusion = "failure"
115
+ self.primitive.jobs.job_run_update(
116
+ job_run["id"],
117
+ status="request_completed",
118
+ conclusion=conclusion,
119
+ stdout=message,
120
+ )
90
121
 
91
122
  sleep(5)
92
123
  except KeyboardInterrupt:
@@ -13,6 +13,7 @@ from .daemons.commands import cli as daemons_commands
13
13
  from .jobs.commands import cli as jobs_commands
14
14
  from .organizations.commands import cli as organizations_commands
15
15
  from .projects.commands import cli as projects_commands
16
+ from .sim.commands import cli as sim_commands
16
17
 
17
18
 
18
19
  @click.group()
@@ -67,6 +68,7 @@ cli.add_command(daemons_commands, "daemons")
67
68
  cli.add_command(jobs_commands, "jobs")
68
69
  cli.add_command(organizations_commands, "organizations")
69
70
  cli.add_command(projects_commands, "projects")
71
+ cli.add_command(sim_commands, "sim")
70
72
 
71
73
  if __name__ == "__main__":
72
74
  cli(obj={})
@@ -4,7 +4,7 @@ from .projects.actions import Projects
4
4
  from .graphql.sdk import create_session
5
5
  from .utils.config import read_config_file
6
6
  from .files.actions import Files
7
- from .simulations.actions import Simulations
7
+ from .sim.actions import Sim
8
8
  from .hardware.actions import Hardware
9
9
  from .lint.actions import Lint
10
10
  from .agent.actions import Agent
@@ -61,7 +61,7 @@ class Primitive:
61
61
  self.projects: Projects = Projects(self)
62
62
  self.jobs: Jobs = Jobs(self)
63
63
  self.files: Files = Files(self)
64
- self.simulations: Simulations = Simulations(self)
64
+ self.sim: Sim = Sim(self)
65
65
  self.hardware: Hardware = Hardware(self)
66
66
  self.lint: Lint = Lint(self)
67
67
  self.agent: Agent = Agent(self)
@@ -6,6 +6,7 @@ HOME_DIRECTORY = Path.home()
6
6
  CURRENT_USER = str(HOME_DIRECTORY.expanduser()).lstrip("/Users/")
7
7
 
8
8
  PRIMITIVE_BINARY_PATH = Path(HOME_DIRECTORY / ".pyenv" / "shims" / "primitive")
9
+
9
10
  PRIMITIVE_LAUNCH_AGENT_FILEPATH = Path(
10
11
  HOME_DIRECTORY / "Library" / "LaunchAgents" / "tech.primitive.agent.plist"
11
12
  )
@@ -78,6 +79,15 @@ def populate_fresh_launch_agent():
78
79
  PRIMITIVE_LAUNCH_AGENT_FILEPATH.parent.mkdir(parents=True, exist_ok=True)
79
80
  PRIMITIVE_LAUNCH_AGENT_FILEPATH.touch()
80
81
 
82
+ found_primitive_binary_path = PRIMITIVE_BINARY_PATH
83
+ if not PRIMITIVE_BINARY_PATH.exists():
84
+ result = subprocess.run(["which", "primitive"], capture_output=True)
85
+ if result.returncode == 0:
86
+ found_primitive_binary_path = result.stdout.decode().rstrip("\n")
87
+ else:
88
+ print("primitive binary not found")
89
+ return False
90
+
81
91
  PRIMITIVE_LAUNCH_AGENT_FILEPATH.write_text(
82
92
  f"""<?xml version="1.0" encoding="UTF-8"?>
83
93
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -96,7 +106,7 @@ def populate_fresh_launch_agent():
96
106
  </array>
97
107
  <key>ProgramArguments</key>
98
108
  <array>
99
- <string>{PRIMITIVE_BINARY_PATH}</string>
109
+ <string>{found_primitive_binary_path}</string>
100
110
  <string>agent</string>
101
111
  </array>
102
112
  <key>RunAtLoad</key>
@@ -82,8 +82,17 @@ def populate_service_file():
82
82
  "After": "network.target",
83
83
  }
84
84
 
85
+ found_primitive_binary_path = PRIMITIVE_BINARY_PATH
86
+ if not PRIMITIVE_BINARY_PATH.exists():
87
+ result = subprocess.run(["which", "primitive"], capture_output=True)
88
+ if result.returncode == 0:
89
+ found_primitive_binary_path = result.stdout.decode().rstrip("\n")
90
+ else:
91
+ print("primitive binary not found")
92
+ return False
93
+
85
94
  config["Service"] = {
86
- "ExecStart": f"{PRIMITIVE_BINARY_PATH} agent",
95
+ "ExecStart": f"{found_primitive_binary_path} agent",
87
96
  "Restart": "always",
88
97
  "StandardError": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
89
98
  "StandardOutput": f"append:{PRIMITIVE_AGENT_LOGS_FILEPATH}",
@@ -125,6 +125,10 @@ fragment JobRunFragment on JobRun {
125
125
  name
126
126
  createdAt
127
127
  updatedAt
128
+ }
129
+ jobSettings {
130
+ containerArgs
131
+ containerCommand
128
132
  }
129
133
  gitCommit {
130
134
  sha
@@ -9,7 +9,7 @@ from ..utils.verible import install_verible
9
9
 
10
10
  class Lint(BaseAction):
11
11
  def execute(self, source: Path = Path.cwd()) -> Tuple[bool, str]:
12
- logger.debug("Starting linter for source: {source}")
12
+ logger.debug(f"Starting linter for source: {source}")
13
13
  files = find_files_for_extension(source, ".sv")
14
14
  if not files:
15
15
  message = "No files found to lint."
@@ -5,7 +5,6 @@ from primitive.utils.actions import BaseAction
5
5
 
6
6
 
7
7
  class Projects(BaseAction):
8
-
9
8
  def get_projects(
10
9
  self,
11
10
  organization_id: Optional[str] = None,
@@ -0,0 +1,190 @@
1
+ from gql import gql
2
+ from pathlib import Path
3
+ from primitive.utils.actions import BaseAction
4
+ from loguru import logger
5
+ import subprocess
6
+ from typing import Tuple, List
7
+ from ..utils.files import find_files_for_extension
8
+ import os
9
+ from .vcd import TokenKind, tokenize
10
+ import io
11
+ from collections import defaultdict
12
+ import urllib
13
+ import json
14
+
15
+
16
+ class Sim(BaseAction):
17
+ def execute(
18
+ self, source: Path = Path.cwd(), cmd: Tuple[str] = ["make"]
19
+ ) -> Tuple[bool, str]:
20
+ logger.debug(f"Starting simulation run for source: {source}")
21
+
22
+ os.chdir(source)
23
+ logger.debug(f"Changed to {source}, starting sim run")
24
+ try:
25
+ result = subprocess.run(cmd, capture_output=True, text=True, env=os.environ)
26
+ except FileNotFoundError:
27
+ message = f"Did not find {cmd}"
28
+ logger.error(message)
29
+ return False, message
30
+
31
+ logger.debug("Sim run complete.")
32
+
33
+ message = ""
34
+ if result.stderr:
35
+ logger.error("\n" + result.stderr)
36
+ if result.stdout:
37
+ logger.info("\n" + result.stdout)
38
+ message = "See above logs for sim output."
39
+
40
+ if result.returncode != 0:
41
+ if not self.primitive.DEBUG:
42
+ message = result.stderr
43
+ return False, message
44
+ else:
45
+ message = "Sim run successful."
46
+
47
+ return True, message
48
+
49
+ def upload_file(self, path: Path) -> str:
50
+ file_upload_response = self.primitive.files.file_upload(
51
+ path, key_prefix=f"{self.job_run_id}/{str(path.parent)}"
52
+ )
53
+ return file_upload_response.json()["data"]["fileUpload"]["id"]
54
+
55
+ def collect_artifacts(self, source) -> None:
56
+ file_ids = []
57
+
58
+ # Look for VCD artifacts
59
+ files = find_files_for_extension(source, ".vcd")
60
+ for file in files:
61
+ trace_file_ids = self.generate_timeseries(path=file)
62
+ file_ids.extend(trace_file_ids)
63
+
64
+ logger.debug("Uploading additional artifacts...")
65
+ files = find_files_for_extension(source, (".xml", ".vcd", ".log", ".history"))
66
+ for file_path in files:
67
+ try:
68
+ file_ids.append(self.upload_file(file_path))
69
+ except FileNotFoundError:
70
+ logger.warning(f"{file_path} not found...")
71
+
72
+ logger.debug("Updating job run...")
73
+ if len(file_ids) > 0:
74
+ job_run_update_response = self.primitive.projects.job_run_update(
75
+ id=self.job_run_id, file_ids=file_ids
76
+ )
77
+ logger.success(job_run_update_response)
78
+
79
+ def generate_timeseries(self, path: Path) -> List[str]:
80
+ logger.debug("Parsing VCD file...")
81
+ with open(path, "rb") as f:
82
+ tokens = tokenize(io.BytesIO(f.read()))
83
+
84
+ metadata = defaultdict(dict)
85
+ traces = defaultdict(list)
86
+ timescale_unit = "s"
87
+ timescale_magnitude = 1
88
+ active_module: str = ""
89
+ time: int = 0
90
+
91
+ for token in tokens:
92
+ match token.kind:
93
+ case TokenKind.TIMESCALE:
94
+ timescale_unit = token.data.unit.value
95
+ timescale_magnitude = token.data.magnitude.value
96
+ case TokenKind.SCOPE:
97
+ active_module = token.data.ident
98
+ case TokenKind.CHANGE_TIME:
99
+ time = int(token.data)
100
+ case TokenKind.VAR:
101
+ var = {
102
+ "id_code": token.data.id_code,
103
+ "module": active_module,
104
+ "var_type": str(token.data.type_),
105
+ "var_size": token.data.size,
106
+ "reference": token.data.reference,
107
+ "bit_index": str(token.data.bit_index),
108
+ }
109
+ metadata[token.data.id_code] = var
110
+ case TokenKind.CHANGE_SCALAR:
111
+ traces[token.data.id_code].append(
112
+ (str(time), str(token.data.value))
113
+ )
114
+ case TokenKind.CHANGE_VECTOR:
115
+ traces[token.data.id_code].append(
116
+ (str(time), str(token.data.value))
117
+ )
118
+
119
+ # Add traces and write files
120
+ logger.debug("Uploading traces...")
121
+ trace_file_ids = []
122
+ for id_code, timeseries in traces.items():
123
+
124
+ def hashed(id_code):
125
+ return urllib.parse.quote_plus(id_code, safe="")
126
+
127
+ file_path = path.parent / f"{hashed(id_code)}.vcd.json"
128
+ with open(file_path, "w") as f:
129
+ f.write(json.dumps(timeseries))
130
+
131
+ trace_file_id = self.upload_file(file_path)
132
+ trace_file_ids.append(trace_file_id)
133
+
134
+ self.trace_create(
135
+ id_code=id_code,
136
+ module=metadata[id_code]["module"],
137
+ var_type=metadata[id_code]["var_type"],
138
+ var_size=metadata[id_code]["var_size"],
139
+ reference=metadata[id_code]["reference"],
140
+ bit_index=metadata[id_code]["bit_index"],
141
+ timescale_unit=timescale_unit,
142
+ timescale_magnitude=timescale_magnitude,
143
+ organization=self.organization_id,
144
+ file=trace_file_id,
145
+ job_run=self.job_run_id,
146
+ )
147
+
148
+ return trace_file_ids
149
+
150
+ def trace_create(
151
+ self,
152
+ id_code: str,
153
+ module: str,
154
+ var_type: str,
155
+ var_size: int,
156
+ reference: str,
157
+ bit_index: str,
158
+ timescale_unit: str,
159
+ timescale_magnitude: int,
160
+ organization: str,
161
+ file: str,
162
+ job_run: str,
163
+ ):
164
+ mutation = gql(
165
+ """
166
+ mutation createTrace($input: TraceCreateInput!) {
167
+ traceCreate(input: $input) {
168
+ ... on Trace {
169
+ id
170
+ }
171
+ }
172
+ }
173
+ """
174
+ )
175
+ input = {
176
+ "idCode": id_code,
177
+ "module": module,
178
+ "varType": var_type,
179
+ "varSize": var_size,
180
+ "reference": reference,
181
+ "bitIndex": bit_index,
182
+ "timescaleUnit": timescale_unit,
183
+ "timescaleMagnitude": timescale_magnitude,
184
+ "organization": organization,
185
+ "file": file,
186
+ "jobRun": job_run,
187
+ }
188
+ variables = {"input": input}
189
+ result = self.primitive.session.execute(mutation, variable_values=variables)
190
+ return result
@@ -0,0 +1,19 @@
1
+ import click
2
+ from pathlib import Path
3
+ import typing
4
+ from typing import Tuple
5
+ from ..utils.printer import print_result
6
+
7
+ if typing.TYPE_CHECKING:
8
+ from ..client import Primitive
9
+
10
+
11
+ @click.command("sim")
12
+ @click.pass_context
13
+ @click.argument("cmd", nargs=-1, required=True)
14
+ @click.option("--source", type=click.Path(exists=True), default=".")
15
+ def cli(context, source: str, cmd: Tuple[str]) -> None:
16
+ """Sim"""
17
+ primitive: Primitive = context.obj.get("PRIMITIVE")
18
+ result, message = primitive.sim.execute(source=Path(source), cmd=cmd)
19
+ print_result(message=message, context=context)