Skip to content

Commit

Permalink
feat: support for taking mod relative paths as input. (#622)
Browse files Browse the repository at this point in the history
* feat: support for taking mod relative paths as input.

* fix: fix path on windows.

* fix: fix windows path.
  • Loading branch information
zong-zhe committed Jul 26, 2023
1 parent 77b6a25 commit 1829787
Show file tree
Hide file tree
Showing 22 changed files with 798 additions and 343 deletions.
565 changes: 263 additions & 302 deletions kclvm/Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions kclvm/cmd/src/test_data/multimod/kcl1/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "kcl1"
edition = "0.0.1"
version = "0.0.1"

1 change: 1 addition & 0 deletions kclvm/cmd/src/test_data/multimod/kcl1/main.k
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kcl1 = 'hello 1'
5 changes: 5 additions & 0 deletions kclvm/cmd/src/test_data/multimod/kcl2/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "kcl2"
edition = "0.0.1"
version = "0.0.1"

1 change: 1 addition & 0 deletions kclvm/cmd/src/test_data/multimod/kcl2/main.k
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
kcl2 = "hello 2"
5 changes: 5 additions & 0 deletions kclvm/cmd/src/test_data/multimod/kcl3/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "kcl3"
edition = "0.0.1"
version = "0.0.1"

5 changes: 5 additions & 0 deletions kclvm/cmd/src/test_data/multimod/kcl3/kcl4/kcl.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[package]
name = "kcl4"
edition = "0.0.1"
version = "0.0.1"

1 change: 1 addition & 0 deletions kclvm/cmd/src/test_data/multimod/kcl3/kcl4/main.k
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
k4 = 'Hello World 4'
1 change: 1 addition & 0 deletions kclvm/cmd/src/test_data/multimod/kcl3/main.k
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
k3 = 'Hello World 3'
132 changes: 130 additions & 2 deletions kclvm/cmd/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,21 @@ use std::{
env,
fs::{self, remove_file},
path::{Path, PathBuf},
sync::Arc,
};

use kclvm_config::modfile::KCL_PKG_PATH;
use kclvm_parser::ParseSession;
use kclvm_runner::exec_program;

use crate::{
app, fmt::fmt_command, lint::lint_command, run::run_command, settings::build_settings,
util::hashmaps_from_matches, vet::vet_command,
app,
fmt::fmt_command,
lint::lint_command,
run::run_command,
settings::{build_settings, must_build_settings},
util::hashmaps_from_matches,
vet::vet_command,
};

#[cfg(unix)]
Expand Down Expand Up @@ -215,6 +223,9 @@ fn test_run_command() {
test_run_command_with_konfig();
test_load_cache_with_different_pkg();
test_kcl_path_is_sym_link();
test_compile_two_kcl_mod();
test_main_pkg_not_found();
test_conflict_mod_file();
}

fn test_run_command_with_import() {
Expand Down Expand Up @@ -364,3 +375,120 @@ fn test_kcl_path_is_sym_link() {
// clean up the symlink
remove_file(link).unwrap();
}

fn test_compile_two_kcl_mod() {
let test_case_path = PathBuf::from("./src/test_data/multimod");

let matches = app().arg_required_else_help(true).get_matches_from(&[
ROOT_CMD,
"run",
&test_case_path.join("kcl1/main.k").display().to_string(),
"${kcl2:KCL_MOD}/main.k",
"-E",
&format!("kcl2={}", test_case_path.join("kcl2").display().to_string()),
]);

let mut buf = Vec::new();
run_command(matches.subcommand_matches("run").unwrap(), &mut buf).unwrap();

assert_eq!(
"kcl1: hello 1\nkcl2: hello 2\n",
String::from_utf8(buf).unwrap()
);

let matches = app().arg_required_else_help(true).get_matches_from(&[
ROOT_CMD,
"run",
&test_case_path.join("kcl2/main.k").display().to_string(),
"${kcl1:KCL_MOD}/main.k",
"-E",
&format!("kcl1={}", test_case_path.join("kcl1").display().to_string()),
]);

let mut buf = Vec::new();
run_command(matches.subcommand_matches("run").unwrap(), &mut buf).unwrap();

assert_eq!(
"kcl2: hello 2\nkcl1: hello 1\n",
String::from_utf8(buf).unwrap()
);

let matches = app().arg_required_else_help(true).get_matches_from(&[
ROOT_CMD,
"run",
&test_case_path.join("kcl3/main.k").display().to_string(),
"${kcl4:KCL_MOD}/main.k",
"-E",
&format!(
"kcl4={}",
test_case_path
.join("kcl3")
.join("kcl4")
.display()
.to_string()
),
]);

let mut buf = Vec::new();
run_command(matches.subcommand_matches("run").unwrap(), &mut buf).unwrap();

assert_eq!(
"k3: Hello World 3\nk4: Hello World 4\n",
String::from_utf8(buf).unwrap()
);

let matches = app().arg_required_else_help(true).get_matches_from(&[
ROOT_CMD,
"run",
&test_case_path
.join("kcl3/kcl4/main.k")
.display()
.to_string(),
"${kcl3:KCL_MOD}/main.k",
"-E",
&format!("kcl3={}", test_case_path.join("kcl3").display().to_string()),
]);

let mut buf = Vec::new();
run_command(matches.subcommand_matches("run").unwrap(), &mut buf).unwrap();

assert_eq!(
"k4: Hello World 4\nk3: Hello World 3\n",
String::from_utf8(buf).unwrap()
);
}

fn test_main_pkg_not_found() {
let test_case_path = PathBuf::from("./src/test_data/multimod");

let matches = app().arg_required_else_help(true).get_matches_from(&[
ROOT_CMD,
"run",
"${kcl3:KCL_MOD}/main.k",
"-E",
&format!("kcl3={}", test_case_path.join("kcl3").display().to_string()),
]);
let settings = must_build_settings(matches.subcommand_matches("run").unwrap());
let sess = Arc::new(ParseSession::default());
match exec_program(sess.clone(), &settings.try_into().unwrap()) {
Ok(_) => panic!("unreachable code."),
Err(msg) => assert_eq!(msg, "No input KCL files"),
}
}

fn test_conflict_mod_file() {
let test_case_path = PathBuf::from("./src/test_data/multimod");

let matches = app().arg_required_else_help(true).get_matches_from(&[
ROOT_CMD,
"run",
&test_case_path.join("kcl1").display().to_string(),
&test_case_path.join("kcl2").display().to_string(),
]);
let settings = must_build_settings(matches.subcommand_matches("run").unwrap());
let sess = Arc::new(ParseSession::default());
match exec_program(sess.clone(), &settings.try_into().unwrap()) {
Ok(_) => panic!("unreachable code."),
Err(msg) => assert!(msg.contains("conflict kcl.mod file paths")),
}
}
1 change: 1 addition & 0 deletions kclvm/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,4 @@ kclvm-version = {path = "../version"}
kclvm-utils = {path = "../utils"}
kclvm-ast = {path = "../ast"}
dirs = "5.0.0"
pcre2 = "0.2.4"
1 change: 1 addition & 0 deletions kclvm/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod cache;
pub mod modfile;
pub mod path;
pub mod settings;
pub mod vfs;

Expand Down
99 changes: 97 additions & 2 deletions kclvm/config/src/modfile.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Copyright 2021 The KCL Authors. All rights reserved.

use indexmap::IndexMap;
use kclvm_utils::path::PathPrefix;
use serde::Deserialize;
use std::{env, fs, io::Read, path::PathBuf};
use std::{collections::HashMap, env, fs, io::Read, path::PathBuf};
use toml;

use crate::path::ModRelativePath;

pub const KCL_MOD_FILE: &str = "kcl.mod";
pub const KCL_FILE_SUFFIX: &str = ".k";
pub const KCL_FILE_EXTENSION: &str = "k";
Expand Down Expand Up @@ -81,6 +84,96 @@ pub struct KCLModFileExpectedSection {
pub global_version: Option<String>,
}

/// [`get_compile_entries_from_paths`] returns a map of package name to package root path.
///
/// # Note
/// If the path in [`file_paths`] is a normal path, the package will be named as `__main__`.
/// If the path in [`file_paths`] is a [`ModRelativePath`], the package will be named by the suffix of [`ModRelativePath`].
///
/// # Error
/// The package root path for package name `__main__` is only used once. If there are multiple
/// package root paths for `__main__`, an error `conflict kcl.mod file` is returned.
///
/// # Example
///
/// ```rust
/// use std::path::PathBuf;
/// use kclvm_config::modfile::get_compile_entries_from_paths;
/// let testpath = PathBuf::from("./src/testdata/multimods").canonicalize().unwrap();
///
/// // [`kcl1_path`] is a normal path of the package [`kcl1`] root directory.
/// // It looks like `/xxx/xxx/xxx`.
/// let kcl1_path = testpath.join("kcl1");
///
/// // [`kcl2_path`] is a mod relative path of the packege [`kcl2`] root directory.
/// // It looks like `${kcl2:KCL_MOD}/xxx/xxx`
/// let kcl2_path = PathBuf::from("${kcl2:KCL_MOD}/main.k");
///
/// // [`external_pkgs`] is a map to show the real path of the mod relative path [`kcl2`].
/// let mut external_pkgs = std::collections::HashMap::<String, String>::new();
/// external_pkgs.insert("kcl2".to_string(), testpath.join("kcl2").to_str().unwrap().to_string());
///
/// // [`get_compile_entries_from_paths`] will return the map of package name to package root real path.
/// let entries = get_compile_entries_from_paths(&[kcl1_path.to_str().unwrap().to_string(), kcl2_path.display().to_string()], external_pkgs).unwrap();
///
/// assert_eq!(entries.len(), 2);
/// assert_eq!(
/// PathBuf::from(entries.get("__main__").unwrap()).canonicalize().unwrap().display().to_string(),
/// kcl1_path.canonicalize().unwrap().to_str().unwrap()
/// );
/// assert_eq!(
/// PathBuf::from(entries.get("kcl2").unwrap()).canonicalize().unwrap().display().to_string(),
/// testpath.join("kcl2").canonicalize().unwrap().to_str().unwrap()
/// );
/// ```
pub fn get_compile_entries_from_paths(
file_paths: &[String],
external_pkgs: HashMap<String, String>,
) -> Result<IndexMap<String, String>, String> {
if file_paths.is_empty() {
return Err("No input KCL files or paths".to_string());
}
let mut result = IndexMap::default();
let mut m = std::collections::HashMap::<String, String>::new();
for s in file_paths {
let path = ModRelativePath::from(s.to_string());
// If the path is a [`ModRelativePath`],
// calculate the real path and the package name.
if let Some((pkg_name, pkg_path)) = path
.get_root_pkg_name()
.map_err(|err| err.to_string())?
.and_then(|name| {
external_pkgs
.get(&name)
.map(|pkg_path: &String| (name, pkg_path))
})
{
let s = path
.canonicalize_by_root_path(pkg_path)
.map_err(|err| err.to_string())?;
if let Some(root) = get_pkg_root(&s) {
result.insert(pkg_name, root);
}
continue;
} else if let Some(root) = get_pkg_root(s) {
// If the path is a normal path.
m.insert(root.clone(), root.clone());
result.insert(kclvm_ast::MAIN_PKG.to_string(), root.clone());
continue;
}
}

if m.is_empty() {
result.insert(kclvm_ast::MAIN_PKG.to_string(), "".to_string());
return Ok(result);
}
if m.len() == 1 {
return Ok(result);
}

Err(format!("conflict kcl.mod file paths: {:?}", m))
}

pub fn get_pkg_root_from_paths(file_paths: &[String]) -> Result<String, String> {
if file_paths.is_empty() {
return Err("No input KCL files or paths".to_string());
Expand All @@ -89,9 +182,11 @@ pub fn get_pkg_root_from_paths(file_paths: &[String]) -> Result<String, String>
let mut m = std::collections::HashMap::<String, String>::new();
let mut last_root = "".to_string();
for s in file_paths {
if s.contains(KCL_MOD_PATH_ENV) {
let path = ModRelativePath::from(s.to_string());
if path.is_relative_path().map_err(|err| err.to_string())? {
continue;
}

if let Some(root) = get_pkg_root(s) {
m.insert(root.clone(), root.clone());
last_root = root.clone();
Expand Down
Loading

0 comments on commit 1829787

Please sign in to comment.