diff --git a/kclvm/api/src/capi_test.rs b/kclvm/api/src/capi_test.rs index 577eaf41b..55a8fd8c5 100644 --- a/kclvm/api/src/capi_test.rs +++ b/kclvm/api/src/capi_test.rs @@ -86,6 +86,15 @@ fn test_c_api_lint_path() { ); } +#[test] +fn test_c_api_call_exec_program_with_compile_only() { + test_c_api_paniced::( + "KclvmService.ExecProgram", + "exec-program-with-compile-only.json", + "exec-program-with-compile-only.response.panic", + ); +} + #[test] fn test_c_api_validate_code() { test_c_api_without_wrapper::( @@ -149,3 +158,47 @@ where kclvm_service_free_string(result_ptr); } } + +fn test_c_api_paniced(svc_name: &str, input: &str, output: &str) +where + A: Message + DeserializeOwned, +{ + let _test_lock = TEST_MUTEX.lock().unwrap(); + let serv = kclvm_service_new(0); + + let input_path = Path::new(TEST_DATA_PATH).join(input); + let input = fs::read_to_string(&input_path) + .unwrap_or_else(|_| panic!("Something went wrong reading {}", input_path.display())); + let args = unsafe { + CString::from_vec_unchecked(serde_json::from_str::(&input).unwrap().encode_to_vec()) + }; + let call = CString::new(svc_name).unwrap(); + let prev_hook = std::panic::take_hook(); + // disable print panic info + std::panic::set_hook(Box::new(|_info| {})); + let result = std::panic::catch_unwind(|| { + kclvm_service_call(serv, call.as_ptr(), args.as_ptr()) as *mut i8 + }); + std::panic::set_hook(prev_hook); + match result { + Ok(result_ptr) => { + let result = unsafe { CStr::from_ptr(result_ptr) }; + let except_result_path = Path::new(TEST_DATA_PATH).join(output); + let except_result_panic_msg = + fs::read_to_string(&except_result_path).unwrap_or_else(|_| { + panic!( + "Something went wrong reading {}", + except_result_path.display() + ) + }); + assert!(result.to_string_lossy().contains(&except_result_panic_msg)); + unsafe { + kclvm_service_delete(serv); + kclvm_service_free_string(result_ptr); + } + } + Err(_) => { + panic!("unreachable code") + } + } +} diff --git a/kclvm/api/src/testdata/exec-program-with-compile-only.json b/kclvm/api/src/testdata/exec-program-with-compile-only.json new file mode 100644 index 000000000..4e6096eee --- /dev/null +++ b/kclvm/api/src/testdata/exec-program-with-compile-only.json @@ -0,0 +1,17 @@ +{ + "work_dir" : "./src/testdata", + "k_filename_list":[ + "test-lint-import.k" + ], + "compile_only": true, + "external_pkgs": [ + { + "pkg_name": "external", + "pkg_path": "./src/testdata_external/external" + }, + { + "pkg_name": "external_1", + "pkg_path": "./src/testdata_external/external_1" + } + ] +} \ No newline at end of file diff --git a/kclvm/api/src/testdata/exec-program-with-compile-only.response.panic b/kclvm/api/src/testdata/exec-program-with-compile-only.response.panic new file mode 100644 index 000000000..86405eaf4 --- /dev/null +++ b/kclvm/api/src/testdata/exec-program-with-compile-only.response.panic @@ -0,0 +1 @@ +Module 'external' imported but unused \ No newline at end of file diff --git a/kclvm/api/src/testdata/test-lint-import.k b/kclvm/api/src/testdata/test-lint-import.k new file mode 100644 index 000000000..c452e62cd --- /dev/null +++ b/kclvm/api/src/testdata/test-lint-import.k @@ -0,0 +1,4 @@ +import external as ext +import external_1 as ext_1 + +a1 = ext_1.a \ No newline at end of file diff --git a/kclvm/runner/src/lib.rs b/kclvm/runner/src/lib.rs index 457f4580c..20e1eeddb 100644 --- a/kclvm/runner/src/lib.rs +++ b/kclvm/runner/src/lib.rs @@ -11,7 +11,9 @@ use kclvm_error::{Diagnostic, Handler}; use kclvm_parser::{load_program, ParseSession}; use kclvm_query::apply_overrides; use kclvm_runtime::{PanicInfo, PlanOptions, ValueRef}; -use kclvm_sema::resolver::{resolve_program, scope::ProgramScope}; +use kclvm_sema::resolver::{ + resolve_program, resolve_program_with_opts, scope::ProgramScope, Options, +}; use linker::Command; pub use runner::ExecProgramArgs; use runner::{ExecProgramResult, KclvmRunner, KclvmRunnerOptions}; @@ -192,9 +194,18 @@ pub fn execute( mut program: Program, args: &ExecProgramArgs, ) -> Result { + // If the user only wants to compile the kcl program, the following code will only resolve ast. + if args.compile_only { + let mut resolve_opts = Options::default(); + resolve_opts.merge_program = false; + // Resolve ast + let scope = resolve_program_with_opts(&mut program, resolve_opts); + emit_compile_diag_to_string(sess, &scope, args.compile_only)?; + return Ok("".to_string()); + } // Resolve ast let scope = resolve_program(&mut program); - emit_compile_diag_to_string(sess, &scope)?; + emit_compile_diag_to_string(sess, &scope, false)?; // Create a temp entry file and the temp dir will be delete automatically let temp_dir = tempdir().map_err(|e| e.to_string())?; @@ -303,13 +314,14 @@ fn temp_file(dir: &str) -> Result { fn emit_compile_diag_to_string( sess: Arc, scope: &ProgramScope, + include_warnings: bool, ) -> Result<(), String> { let mut res_str = sess .1 .borrow_mut() .emit_to_string() .map_err(|err| err.to_string())?; - let sema_err = scope.emit_diagnostics_to_string(sess.0.clone()); + let sema_err = scope.emit_diagnostics_to_string(sess.0.clone(), include_warnings); if sema_err.is_err() { #[cfg(not(target_os = "windows"))] res_str.push_str("\n"); diff --git a/kclvm/runner/src/runner.rs b/kclvm/runner/src/runner.rs index 9cabaac12..ed3b5fd28 100644 --- a/kclvm/runner/src/runner.rs +++ b/kclvm/runner/src/runner.rs @@ -50,6 +50,8 @@ pub struct ExecProgramArgs { pub sort_keys: bool, /// Whether including schema type in JSON/YAML result pub include_schema_type_path: bool, + // Whether to compile only. + pub compile_only: bool, // plugin_agent is the address of plugin. #[serde(skip)] pub plugin_agent: u64, diff --git a/kclvm/runner/src/test_datas/exec_prog_args/default.json b/kclvm/runner/src/test_datas/exec_prog_args/default.json index ac565a968..812b7f420 100644 --- a/kclvm/runner/src/test_datas/exec_prog_args/default.json +++ b/kclvm/runner/src/test_datas/exec_prog_args/default.json @@ -1 +1 @@ -{"work_dir":null,"k_filename_list":[],"external_pkgs":[],"k_code_list":[],"args":[],"overrides":[],"disable_yaml_result":false,"print_override_ast":false,"strict_range_check":false,"disable_none":false,"verbose":0,"debug":0,"sort_keys":false,"include_schema_type_path":false} \ No newline at end of file +{"work_dir":null,"k_filename_list":[],"external_pkgs":[],"k_code_list":[],"args":[],"overrides":[],"disable_yaml_result":false,"print_override_ast":false,"strict_range_check":false,"disable_none":false,"verbose":0,"debug":0,"sort_keys":false,"include_schema_type_path":false,"compile_only":false} \ No newline at end of file diff --git a/kclvm/runner/src/test_datas/settings_file/settings.json b/kclvm/runner/src/test_datas/settings_file/settings.json index 1edf02279..09c41845d 100644 --- a/kclvm/runner/src/test_datas/settings_file/settings.json +++ b/kclvm/runner/src/test_datas/settings_file/settings.json @@ -1 +1 @@ -{"work_dir":null,"k_filename_list":["../main.k","./before/base.k","./main.k","./sub/sub.k"],"external_pkgs":[],"k_code_list":[],"args":[{"name":"app-name","value":"\"kclvm\""},{"name":"image","value":"\"kclvm:v0.0.1\""}],"overrides":[],"disable_yaml_result":false,"print_override_ast":false,"strict_range_check":false,"disable_none":false,"verbose":0,"debug":0,"sort_keys":false,"include_schema_type_path":false} \ No newline at end of file +{"work_dir":null,"k_filename_list":["../main.k","./before/base.k","./main.k","./sub/sub.k"],"external_pkgs":[],"k_code_list":[],"args":[{"name":"app-name","value":"\"kclvm\""},{"name":"image","value":"\"kclvm:v0.0.1\""}],"overrides":[],"disable_yaml_result":false,"print_override_ast":false,"strict_range_check":false,"disable_none":false,"verbose":0,"debug":0,"sort_keys":false,"include_schema_type_path":false,"compile_only":false} \ No newline at end of file diff --git a/kclvm/sema/src/resolver/scope.rs b/kclvm/sema/src/resolver/scope.rs index 8c25ccd20..c2b2666ad 100644 --- a/kclvm/sema/src/resolver/scope.rs +++ b/kclvm/sema/src/resolver/scope.rs @@ -297,13 +297,20 @@ impl ProgramScope { } /// Return diagnostic pretty string but do not abort if the session exists any diagnostic. - pub fn emit_diagnostics_to_string(&self, sess: Arc) -> Result<(), String> { + pub fn emit_diagnostics_to_string( + &self, + sess: Arc, + include_warning: bool, + ) -> Result<(), String> { let emit_error = || -> anyhow::Result<()> { // Add resolve errors into the session for diag in &self.handler.diagnostics { if matches!(diag.level, Level::Error) { sess.add_err(diag.clone())?; } + if include_warning && matches!(diag.level, Level::Warning) { + sess.add_err(diag.clone())?; + } } // If has syntax and resolve errors, return its string format. if sess.diag_handler.has_errors()? { diff --git a/kclvm/spec/gpyrpc/gpyrpc.proto b/kclvm/spec/gpyrpc/gpyrpc.proto index e3bcac6b8..c13004e94 100644 --- a/kclvm/spec/gpyrpc/gpyrpc.proto +++ b/kclvm/spec/gpyrpc/gpyrpc.proto @@ -149,6 +149,9 @@ message ExecProgram_Args { // Whether including schema type in JSON/YAML result bool include_schema_type_path = 14; + + // Whether only compiling the program + bool compile_only = 15; } message ExecProgram_Result {