fix subdirs (opening kcl-samples from kcl-samples dir) (#5171)
* WIP: Add the KCL file path into the executor * reuse the stuff that works with settings.project_directory Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes on both sides Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes on both sides Signed-off-by: Jess Frazelle <github@jessfraz.com> * helper method Signed-off-by: Jess Frazelle <github@jessfraz.com> * fixes Signed-off-by: Jess Frazelle <github@jessfraz.com> * update kcl-samples tests to not change dirs Signed-off-by: Jess Frazelle <github@jessfraz.com> * update kcl-samples tests to not change dirs Signed-off-by: Jess Frazelle <github@jessfraz.com> * fmt Signed-off-by: Jess Frazelle <github@jessfraz.com> --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
This commit is contained in:
@ -322,6 +322,7 @@ export class KclManager {
|
||||
await this.ensureWasmInit()
|
||||
const { logs, errors, execState, isInterrupted } = await executeAst({
|
||||
ast,
|
||||
path: codeManager.currentFilePath || undefined,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
})
|
||||
|
||||
|
@ -80,6 +80,10 @@ export default class CodeManager {
|
||||
}))
|
||||
}
|
||||
|
||||
get currentFilePath(): string | null {
|
||||
return this._currentFilePath
|
||||
}
|
||||
|
||||
updateCurrentFilePath(path: string) {
|
||||
this._currentFilePath = path
|
||||
}
|
||||
|
@ -52,27 +52,22 @@ afterAll(async () => {
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.chdir('..')
|
||||
})
|
||||
|
||||
// The tests have to be sequential because we need to change directories
|
||||
// to support `import` working properly.
|
||||
// @ts-expect-error
|
||||
describe.sequential('Test KCL Samples from public Github repository', () => {
|
||||
// @ts-expect-error
|
||||
describe.sequential('when performing enginelessExecutor', () => {
|
||||
describe('Test KCL Samples from public Github repository', () => {
|
||||
describe('when performing enginelessExecutor', () => {
|
||||
manifest.forEach((file: KclSampleFile) => {
|
||||
// @ts-expect-error
|
||||
it.sequential(
|
||||
it(
|
||||
`should execute ${file.title} (${file.file}) successfully`,
|
||||
async () => {
|
||||
const [dirProject, fileKcl] =
|
||||
file.pathFromProjectDirectoryToFirstFile.split('/')
|
||||
process.chdir(dirProject)
|
||||
const code = await fs.readFile(fileKcl, 'utf-8')
|
||||
const code = await fs.readFile(
|
||||
file.pathFromProjectDirectoryToFirstFile,
|
||||
'utf-8'
|
||||
)
|
||||
const ast = assertParse(code)
|
||||
await enginelessExecutor(ast, programMemoryInit())
|
||||
await enginelessExecutor(
|
||||
ast,
|
||||
programMemoryInit(),
|
||||
file.pathFromProjectDirectoryToFirstFile
|
||||
)
|
||||
},
|
||||
files.length * 1000
|
||||
)
|
||||
|
@ -46,12 +46,14 @@ export const toolTips: Array<ToolTip> = [
|
||||
|
||||
export async function executeAst({
|
||||
ast,
|
||||
path,
|
||||
engineCommandManager,
|
||||
// If you set programMemoryOverride we assume you mean mock mode. Since that
|
||||
// is the only way to go about it.
|
||||
programMemoryOverride,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
path?: string
|
||||
engineCommandManager: EngineCommandManager
|
||||
programMemoryOverride?: ProgramMemory
|
||||
isInterrupted?: boolean
|
||||
@ -63,8 +65,8 @@ export async function executeAst({
|
||||
}> {
|
||||
try {
|
||||
const execState = await (programMemoryOverride
|
||||
? enginelessExecutor(ast, programMemoryOverride)
|
||||
: executor(ast, engineCommandManager))
|
||||
? enginelessExecutor(ast, programMemoryOverride, path)
|
||||
: executor(ast, engineCommandManager, path))
|
||||
|
||||
await engineCommandManager.waitForAllCommands()
|
||||
|
||||
|
@ -31,6 +31,9 @@ class FileSystemManager {
|
||||
}
|
||||
|
||||
async join(dir: string, path: string): Promise<string> {
|
||||
if (path.startsWith(dir)) {
|
||||
path = path.slice(dir.length)
|
||||
}
|
||||
return Promise.resolve(window.electron.path.join(dir, path))
|
||||
}
|
||||
|
||||
|
@ -566,9 +566,19 @@ export function sketchFromKclValue(
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a KCL program.
|
||||
* @param node The AST of the program to execute.
|
||||
* @param path The full path of the file being executed. Use `null` for
|
||||
* expressions that don't have a file, like expressions in the command bar.
|
||||
* @param programMemoryOverride If this is not `null`, this will be used as the
|
||||
* initial program memory, and the execution will be engineless (AKA mock
|
||||
* execution).
|
||||
*/
|
||||
export const executor = async (
|
||||
node: Node<Program>,
|
||||
engineCommandManager: EngineCommandManager,
|
||||
path?: string,
|
||||
programMemoryOverride: ProgramMemory | Error | null = null
|
||||
): Promise<ExecState> => {
|
||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||
@ -590,6 +600,7 @@ export const executor = async (
|
||||
}
|
||||
const execOutcome: RustExecOutcome = await execute(
|
||||
JSON.stringify(node),
|
||||
path,
|
||||
JSON.stringify(programMemoryOverride?.toRaw() || null),
|
||||
JSON.stringify({ settings: jsAppSettings }),
|
||||
engineCommandManager,
|
||||
|
@ -80,7 +80,8 @@ class MockEngineCommandManager {
|
||||
|
||||
export async function enginelessExecutor(
|
||||
ast: Node<Program>,
|
||||
pmo: ProgramMemory | Error = ProgramMemory.empty()
|
||||
pmo: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
path?: string
|
||||
): Promise<ExecState> {
|
||||
if (pmo !== null && err(pmo)) return Promise.reject(pmo)
|
||||
|
||||
@ -90,7 +91,7 @@ export async function enginelessExecutor(
|
||||
}) as any as EngineCommandManager
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
mockEngineCommandManager.startNewSession()
|
||||
const execState = await executor(ast, mockEngineCommandManager, pmo)
|
||||
const execState = await executor(ast, mockEngineCommandManager, path, pmo)
|
||||
await mockEngineCommandManager.waitForAllCommands()
|
||||
return execState
|
||||
}
|
||||
|
@ -131,7 +131,7 @@ pub struct ExecOutcome {
|
||||
impl ExecState {
|
||||
pub fn new(exec_settings: &ExecutorSettings) -> Self {
|
||||
ExecState {
|
||||
global: GlobalState::new(),
|
||||
global: GlobalState::new(exec_settings),
|
||||
mod_local: ModuleState::new(exec_settings),
|
||||
}
|
||||
}
|
||||
@ -142,7 +142,7 @@ impl ExecState {
|
||||
// This is for the front end to keep track of the ids.
|
||||
id_generator.next_id = 0;
|
||||
|
||||
let mut global = GlobalState::new();
|
||||
let mut global = GlobalState::new(exec_settings);
|
||||
global.id_generator = id_generator;
|
||||
|
||||
*self = ExecState {
|
||||
@ -204,7 +204,7 @@ impl ExecState {
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
fn new() -> Self {
|
||||
fn new(settings: &ExecutorSettings) -> Self {
|
||||
let mut global = GlobalState {
|
||||
id_generator: Default::default(),
|
||||
path_to_source_id: Default::default(),
|
||||
@ -215,9 +215,8 @@ impl GlobalState {
|
||||
artifact_graph: Default::default(),
|
||||
};
|
||||
|
||||
// TODO(#4434): Use the top-level file's path.
|
||||
let root_path = PathBuf::new();
|
||||
let root_id = ModuleId::default();
|
||||
let root_path = settings.current_file.clone().unwrap_or_default();
|
||||
global.module_infos.insert(
|
||||
root_id,
|
||||
ModuleInfo {
|
||||
@ -1752,6 +1751,9 @@ pub struct ExecutorSettings {
|
||||
/// The directory of the current project. This is used for resolving import
|
||||
/// paths. If None is given, the current working directory is used.
|
||||
pub project_directory: Option<PathBuf>,
|
||||
/// This is the path to the current file being executed.
|
||||
/// We use this for preventing cyclic imports.
|
||||
pub current_file: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for ExecutorSettings {
|
||||
@ -1763,6 +1765,7 @@ impl Default for ExecutorSettings {
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1776,6 +1779,7 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
|
||||
show_grid: config.settings.modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1789,6 +1793,7 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
|
||||
show_grid: config.settings.modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1802,6 +1807,25 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
|
||||
show_grid: modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutorSettings {
|
||||
/// Add the current file path to the executor settings.
|
||||
pub fn with_current_file(&mut self, current_file: PathBuf) {
|
||||
// We want the parent directory of the file.
|
||||
if current_file.extension() == Some(std::ffi::OsStr::new("kcl")) {
|
||||
self.current_file = Some(current_file.clone());
|
||||
// Get the parent directory.
|
||||
if let Some(parent) = current_file.parent() {
|
||||
self.project_directory = Some(parent.to_path_buf());
|
||||
} else {
|
||||
self.project_directory = Some(std::path::PathBuf::from(""));
|
||||
}
|
||||
} else {
|
||||
self.project_directory = Some(current_file.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2019,6 +2043,7 @@ impl ExecutorContext {
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
},
|
||||
None,
|
||||
engine_addr,
|
||||
@ -2578,7 +2603,20 @@ impl ExecutorContext {
|
||||
let info = exec_state.global.module_infos[&module_id].clone();
|
||||
|
||||
match &info.repr {
|
||||
ModuleRepr::Root => unreachable!(),
|
||||
ModuleRepr::Root => Err(KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
exec_state
|
||||
.mod_local
|
||||
.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
info.path.display()
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})),
|
||||
ModuleRepr::Kcl(program) => {
|
||||
let mut local_state = ModuleState {
|
||||
import_stack: exec_state.mod_local.import_stack.clone(),
|
||||
|
@ -87,7 +87,7 @@ async fn execute(test_name: &str, render_to_png: bool) {
|
||||
let exec_res = crate::test_server::execute_and_snapshot_ast(
|
||||
ast.into(),
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
Some(Path::new("tests").join(test_name)),
|
||||
Some(Path::new("tests").join(test_name).join("input.kcl").to_owned()),
|
||||
)
|
||||
.await;
|
||||
match exec_res {
|
||||
|
@ -21,9 +21,9 @@ pub struct RequestBody {
|
||||
pub async fn execute_and_snapshot(
|
||||
code: &str,
|
||||
units: UnitLength,
|
||||
project_directory: Option<PathBuf>,
|
||||
current_file: Option<PathBuf>,
|
||||
) -> Result<image::DynamicImage, ExecError> {
|
||||
let ctx = new_context(units, true, project_directory).await?;
|
||||
let ctx = new_context(units, true, current_file).await?;
|
||||
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
let res = do_execute_and_snapshot(&ctx, program)
|
||||
.await
|
||||
@ -38,9 +38,9 @@ pub async fn execute_and_snapshot(
|
||||
pub async fn execute_and_snapshot_ast(
|
||||
ast: Program,
|
||||
units: UnitLength,
|
||||
project_directory: Option<PathBuf>,
|
||||
current_file: Option<PathBuf>,
|
||||
) -> Result<(ExecState, image::DynamicImage), ExecErrorWithState> {
|
||||
let ctx = new_context(units, true, project_directory).await?;
|
||||
let ctx = new_context(units, true, current_file).await?;
|
||||
let res = do_execute_and_snapshot(&ctx, ast).await;
|
||||
ctx.close().await;
|
||||
res
|
||||
@ -49,9 +49,9 @@ pub async fn execute_and_snapshot_ast(
|
||||
pub async fn execute_and_snapshot_no_auth(
|
||||
code: &str,
|
||||
units: UnitLength,
|
||||
project_directory: Option<PathBuf>,
|
||||
current_file: Option<PathBuf>,
|
||||
) -> Result<image::DynamicImage, ExecError> {
|
||||
let ctx = new_context(units, false, project_directory).await?;
|
||||
let ctx = new_context(units, false, current_file).await?;
|
||||
let program = Program::parse_no_errs(code).map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
let res = do_execute_and_snapshot(&ctx, program)
|
||||
.await
|
||||
@ -88,7 +88,7 @@ async fn do_execute_and_snapshot(
|
||||
pub async fn new_context(
|
||||
units: UnitLength,
|
||||
with_auth: bool,
|
||||
project_directory: Option<PathBuf>,
|
||||
current_file: Option<PathBuf>,
|
||||
) -> Result<ExecutorContext, ConnectionError> {
|
||||
let mut client = new_zoo_client(if with_auth { None } else { Some("bad_token".to_string()) }, None)
|
||||
.map_err(ConnectionError::CouldNotMakeClient)?;
|
||||
@ -99,17 +99,19 @@ pub async fn new_context(
|
||||
client.set_base_url("https://api.zoo.dev".to_string());
|
||||
}
|
||||
|
||||
let ctx = ExecutorContext::new(
|
||||
&client,
|
||||
ExecutorSettings {
|
||||
let mut settings = ExecutorSettings {
|
||||
units,
|
||||
highlight_edges: true,
|
||||
enable_ssao: false,
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
project_directory,
|
||||
},
|
||||
)
|
||||
project_directory: None,
|
||||
current_file: None,
|
||||
};
|
||||
if let Some(current_file) = current_file {
|
||||
settings.with_current_file(current_file);
|
||||
}
|
||||
let ctx = ExecutorContext::new(&client, settings)
|
||||
.await
|
||||
.map_err(ConnectionError::Establishing)?;
|
||||
Ok(ctx)
|
||||
|
@ -1,12 +1,13 @@
|
||||
---
|
||||
source: kcl/src/simulation_tests.rs
|
||||
description: Error from executing import_cycle1.kcl
|
||||
snapshot_kind: text
|
||||
---
|
||||
KCL ImportCycle error
|
||||
|
||||
× import cycle: circular import of modules is not allowed: tests/
|
||||
│ import_cycle1/import_cycle2.kcl -> tests/import_cycle1/import_cycle3.kcl
|
||||
│ -> tests/import_cycle1/input.kcl -> tests/import_cycle1/import_cycle2.kcl
|
||||
│ -> tests/import_cycle1/input.kcl
|
||||
╭─[1:1]
|
||||
1 │ import two from "import_cycle2.kcl"
|
||||
· ───────────────────────────────────
|
||||
|
@ -58,6 +58,7 @@ pub async fn clear_scene_and_bust_cache(
|
||||
#[wasm_bindgen]
|
||||
pub async fn execute(
|
||||
program_ast_json: &str,
|
||||
path: Option<String>,
|
||||
program_memory_override_str: &str,
|
||||
settings: &str,
|
||||
engine_manager: kcl_lib::wasm_engine::EngineCommandManager,
|
||||
@ -73,11 +74,16 @@ pub async fn execute(
|
||||
// You cannot override the memory in non-mock mode.
|
||||
let is_mock = program_memory_override.is_some();
|
||||
|
||||
let settings: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
|
||||
let config: kcl_lib::Configuration = serde_json::from_str(settings).map_err(|e| e.to_string())?;
|
||||
let mut settings: kcl_lib::ExecutorSettings = config.into();
|
||||
if let Some(path) = path {
|
||||
settings.with_current_file(std::path::PathBuf::from(path));
|
||||
}
|
||||
|
||||
let ctx = if is_mock {
|
||||
kcl_lib::ExecutorContext::new_mock(fs_manager, settings.into()).await?
|
||||
kcl_lib::ExecutorContext::new_mock(fs_manager, settings).await?
|
||||
} else {
|
||||
kcl_lib::ExecutorContext::new(engine_manager, fs_manager, settings.into()).await?
|
||||
kcl_lib::ExecutorContext::new(engine_manager, fs_manager, settings).await?
|
||||
};
|
||||
|
||||
let mut exec_state = ExecState::new(&ctx.settings);
|
||||
|
Reference in New Issue
Block a user