Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support for taking mod relative paths as input. #622

Merged
merged 3 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading