270 engine_root: str | os.PathLike[str],
271 cache_dir: str | os.PathLike[str] |
None =
None,
272 compiler: str | os.PathLike[str] |
None =
None,
273 eigen_include: str | os.PathLike[str] |
None =
None,
274 plugin_validator: Callable[[Path, int], Mapping[str, Any]] |
None =
None,
275 lock_timeout_seconds: float = 120.0,
276 compile_timeout_seconds: float = 300.0,
280 Path(cache_dir).expanduser().resolve()
282 else _default_cache_dir()
284 self.
compiler = os.fspath(compiler)
if compiler
is not None else None
286 Path(eigen_include).expanduser().resolve()
287 if eigen_include
is not None
294 def build(self, pine_source: str, *, filename: str =
"<input>") -> StrategyArtifact:
295 if not isinstance(pine_source, str):
296 raise TypeError(
"pine_source must be a string")
300 engine = self._resolve_engine_layout()
301 compiler = self._resolve_compiler_identity()
302 eigen = self._resolve_eigen_identity(engine)
304 codegen = codegen_identity()
305 except ModuleNotFoundError
as error:
308 "pineforge-codegen is required to compile PineScript; install "
309 "pineforge-hpo[transpile] or provide a precompiled artifact",
311 output_extension, link_mode = self._platform_link_mode()
313 "flags": list(CANONICAL_COMPILE_FLAGS),
315 str(path)
for path
in (*engine.include_dirs, eigen.include_dir)
317 "link_mode": link_mode,
318 "output_extension": output_extension,
321 "source_sha256": hashlib.sha256(pine_source.encode(
"utf-8")).hexdigest(),
322 "codegen": codegen.to_dict(),
323 "engine": engine.identity_dict(),
324 "compiler": compiler.to_dict(),
325 "eigen": eigen.to_dict(),
327 "system": platform.system(),
328 "release": platform.release(),
329 "machine": platform.machine(),
330 "sys_platform": sys.platform,
331 "python_implementation": platform.python_implementation(),
332 "python_version": platform.python_version(),
334 "compiler_environment": {
335 key: os.environ.get(key,
"")
for key
in _COMPILER_ENV_KEYS
337 "compile": compile_spec,
339 request_key = _json_sha256(request_identity)
341 hit = self._lookup_request(request_key, request_identity, output_extension)
345 lock_path = self.
cache_dir /
"locks" / f
"{request_key}.lock"
347 hit = self._lookup_request(request_key, request_identity, output_extension)
351 transpile_result = transpile_source(pine_source, filename=filename)
353 generated_cpp = transpile_result.require_success()
354 except TranspileFailure
as error:
358 diagnostics=transpile_result.diagnostics,
361 generated_cpp_sha256 = hashlib.sha256(
362 generated_cpp.encode(
"utf-8")
364 artifact_identity = {
365 "request_identity": request_identity,
366 "generated_cpp_sha256": generated_cpp_sha256,
368 artifact_key = _json_sha256(artifact_identity)
369 existing = self._load_artifact(
370 artifact_key=artifact_key,
371 request_key=request_key,
372 request_identity=request_identity,
373 output_extension=output_extension,
376 if existing
is not None:
377 self._write_request_index(request_key, artifact_key)
380 artifact = self._compile_and_publish(
381 artifact_key=artifact_key,
382 request_key=request_key,
383 request_identity=request_identity,
384 output_extension=output_extension,
385 generated_cpp=generated_cpp,
386 generated_cpp_sha256=generated_cpp_sha256,
387 inputs=transpile_result.inputs,
388 strategy_params=transpile_result.strategy_params,
389 diagnostics=transpile_result.diagnostics,
390 source_name=filename,
395 self._write_request_index(request_key, artifact_key)