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: treat kcl.mod file as a compile unit #1348

Merged
merged 1 commit into from
May 22, 2024
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
2 changes: 0 additions & 2 deletions kclvm/config/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ pub const KCL_CACHE_PATH_ENV_VAR: &str = "KCL_CACHE_PATH";
pub type CacheInfo = Vec<u8>;
pub type Cache = HashMap<String, CacheInfo>;

#[allow(dead_code)]
pub struct CacheOption {
cache_dir: String,
}
Expand Down Expand Up @@ -150,7 +149,6 @@ fn get_cache_dir(root: &str, cache_dir: Option<&str>) -> String {
}

#[inline]
#[allow(dead_code)]
fn get_cache_filename(root: &str, target: &str, pkgpath: &str, cache_dir: Option<&str>) -> String {
let cache_dir = cache_dir.unwrap_or(DEFAULT_CACHE_DIR);
let root = std::env::var(KCL_CACHE_PATH_ENV_VAR).unwrap_or(root.to_string());
Expand Down
225 changes: 158 additions & 67 deletions kclvm/config/src/modfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,133 @@

use anyhow::Result;
use kclvm_utils::path::PathPrefix;
use serde::Deserialize;
use std::{env, fs, io::Read, path::PathBuf};
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
env, fs,
io::Read,
path::{Path, PathBuf},
};
use toml;

use crate::path::ModRelativePath;

pub const KCL_MOD_FILE: &str = "kcl.mod";
pub const KCL_MOD_LOCK_FILE: &str = "kcl.mod.lock";
pub const KCL_FILE_SUFFIX: &str = ".k";
pub const KCL_FILE_EXTENSION: &str = "k";
pub const KCL_MOD_PATH_ENV: &str = "${KCL_MOD}";
pub const KCL_PKG_PATH: &str = "KCL_PKG_PATH";
pub const DEFAULT_KCL_HOME: &str = ".kcl";
pub const DEFAULT_KPM_SUBDIR: &str = "kpm";

/// ModFile is kcl package file 'kcl.mod'.
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct ModFile {
pub package: Option<Package>,
pub profile: Option<Profile>,
pub dependencies: Option<Dependencies>,
}

/// Package is the kcl package section of 'kcl.mod'.
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Package {
/// The name of the package.
pub name: Option<String>,
/// The kcl compiler version
pub edition: Option<String>,
/// The version of the package.
pub version: Option<String>,
/// Description denotes the description of the package.
pub description: Option<String>,
/// Exclude denote the files to include when publishing.
pub include: Option<Vec<String>>,
/// Exclude denote the files to exclude when publishing.
pub exclude: Option<Vec<String>>,
}

/// Profile is the profile section of 'kcl.mod'.
/// It is used to specify the compilation options of the current package.
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct Profile {
/// A list of entry-point files.
pub entries: Option<Vec<String>>,
/// Flag that, when true, disables the emission of the special 'none' value in the output.
pub disable_none: Option<bool>,
/// Flag that, when true, ensures keys in maps are sorted.
pub sort_keys: Option<bool>,
/// A list of attribute selectors for conditional compilation.
pub selectors: Option<Vec<String>>,
/// A list of override paths.
pub overrides: Option<Vec<String>>,
/// A list of additional options for the KCL compiler.
pub options: Option<Vec<String>>,
}

/// A map of package names to their respective dependency specifications.
pub type Dependencies = HashMap<String, Dependency>;

/// Dependency represents a single dependency for a package, which may come in different forms
/// such as version, Git repository, OCI repository, or a local path.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum Dependency {
/// Specifies a version dependency, e.g., "1.0.0".
Version(String),
/// Specifies a Git source dependency.
Git(GitSource),
/// Specifies an OCI (Open Container Initiative) image source dependency.
Oci(OciSource),
/// Specifies a local path dependency.
Local(LocalSource),
}

#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct GitSource {
/// The URL of the Git repository.
pub git: String,
/// An optional branch name within the Git repository.
pub branch: Option<String>,
/// An optional commit hash to check out from the Git repository.
pub commit: Option<String>,
/// An optional tag name to check out from the Git repository.
pub tag: Option<String>,
/// An optional version specification associated with Git source.
pub version: Option<String>,
}

/// Defines an OCI package as a source for a dependency.
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct OciSource {
// The URI of the OCI repository.
pub oci: String,
/// An optional tag of the OCI package in the registry.
pub tag: Option<String>,
}

/// Defines a local filesystem path as a source for a dependency.
#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)]
pub struct LocalSource {
/// The path to the local directory or file.
pub path: String,
}

impl ModFile {
#[inline]
pub fn get_entries(&self) -> Option<Vec<String>> {
self.profile.as_ref().map(|p| p.entries.clone()).flatten()
}
}

/// Load kcl mod file from path
pub fn load_mod_file<P: AsRef<Path>>(path: P) -> Result<ModFile> {
let file_path = path.as_ref().join(KCL_MOD_FILE);
let mut file = std::fs::File::open(file_path)?;
let mut buffer: Vec<u8> = vec![];
file.read_to_end(&mut buffer)?;
toml::from_slice(buffer.as_slice()).map_err(|e| anyhow::anyhow!(e))
}

/// Get the path holding the external kcl package.
/// From the environment variable KCL_PKG_PATH.
/// If `KCL_PKG_PATH` is not present, then the user root string is returned.
Expand Down Expand Up @@ -51,39 +164,16 @@ pub fn create_default_vendor_home() -> Option<String> {
match kpm_home.canonicalize() {
Ok(path) => return Some(path.display().to_string()),
Err(_) => match fs::create_dir_all(kpm_home.clone()) {
Ok(_) => return Some(kpm_home.canonicalize().unwrap().display().to_string()),
Ok(_) => match kpm_home.canonicalize() {
Ok(p) => Some(p.display().to_string()),
Err(_) => None,
},
Err(_) => None,
},
}
}

#[allow(dead_code)]
#[derive(Default, Deserialize)]
pub struct KCLModFile {
pub root: Option<String>,
pub root_pkg: Option<String>,
pub build: Option<KCLModFileBuildSection>,
pub expected: Option<KCLModFileExpectedSection>,
}

#[allow(dead_code)]
#[derive(Default, Deserialize)]
pub struct KCLModFileBuildSection {
pub enable_pkg_cache: Option<bool>,
pub cached_pkg_prefix: Option<String>,
pub target: Option<String>,
}

#[allow(dead_code)]
#[derive(Default, Deserialize)]
pub struct KCLModFileExpectedSection {
pub min_build_time: Option<String>,
pub max_build_time: Option<String>,
pub kclvm_version: Option<String>,
pub kcl_plugin_version: Option<String>,
pub global_version: Option<String>,
}

/// Get package root path from input file paths and workdir.
pub fn get_pkg_root_from_paths(file_paths: &[String], workdir: String) -> Result<String, String> {
if file_paths.is_empty() {
return Err("No input KCL files or paths".to_string());
Expand Down Expand Up @@ -114,6 +204,7 @@ pub fn get_pkg_root_from_paths(file_paths: &[String], workdir: String) -> Result
}
}

/// Get package root path from the single input file path.
pub fn get_pkg_root(k_file_path: &str) -> Option<String> {
if k_file_path.is_empty() {
return None;
Expand Down Expand Up @@ -143,17 +234,6 @@ pub fn get_pkg_root(k_file_path: &str) -> Option<String> {
None
}

pub fn load_mod_file(root: &str) -> KCLModFile {
let k_mod_file_path = std::path::Path::new(root).join(KCL_MOD_FILE);
if !k_mod_file_path.exists() {
return KCLModFile::default();
}
let mut file = std::fs::File::open(k_mod_file_path.to_str().unwrap()).unwrap();
let mut buffer: Vec<u8> = vec![];
file.read_to_end(&mut buffer).unwrap();
toml::from_slice(buffer.as_slice()).unwrap()
}

#[cfg(test)]
mod modfile_test {
use crate::modfile::*;
Expand Down Expand Up @@ -190,37 +270,48 @@ mod modfile_test {

#[test]
fn test_load_mod_file() {
let kcl_mod = load_mod_file(TEST_ROOT);
assert!(kcl_mod.build.as_ref().unwrap().enable_pkg_cache.unwrap());
let kcl_mod = load_mod_file(TEST_ROOT).unwrap();
assert_eq!(
kcl_mod.package.as_ref().unwrap().name.as_ref().unwrap(),
"test_add_deps"
);
assert_eq!(
kcl_mod.package.as_ref().unwrap().version.as_ref().unwrap(),
"0.0.1"
);
assert_eq!(
kcl_mod.package.as_ref().unwrap().edition.as_ref().unwrap(),
"0.0.1"
);
assert_eq!(
kcl_mod.profile.as_ref().unwrap().entries.as_ref().unwrap(),
&vec!["main.k".to_string()]
);
assert_eq!(
kcl_mod.dependencies.as_ref().unwrap().get("pkg0"),
Some(&Dependency::Git(GitSource {
git: "test_url".to_string(),
tag: Some("test_tag".to_string()),
..Default::default()
}))
);
assert_eq!(
kcl_mod
.build
.as_ref()
.unwrap()
.cached_pkg_prefix
.as_ref()
.unwrap(),
"pkg.path"
kcl_mod.dependencies.as_ref().unwrap().get("pkg1"),
Some(&Dependency::Version("oci_tag1".to_string()))
);
assert_eq!(
kcl_mod
.expected
.as_ref()
.unwrap()
.kclvm_version
.as_ref()
.unwrap(),
"v0.3.0"
kcl_mod.dependencies.as_ref().unwrap().get("pkg2"),
Some(&Dependency::Oci(OciSource {
oci: "oci://ghcr.io/kcl-lang/helloworld".to_string(),
tag: Some("0.1.1".to_string()),
..Default::default()
}))
);
assert_eq!(
kcl_mod
.expected
.as_ref()
.unwrap()
.kcl_plugin_version
.as_ref()
.unwrap(),
"v0.2.0"
kcl_mod.dependencies.as_ref().unwrap().get("pkg3"),
Some(&Dependency::Local(LocalSource {
path: "../pkg".to_string(),
}))
);
}
}
19 changes: 13 additions & 6 deletions kclvm/config/src/testdata/kcl.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
[build]
enable_pkg_cache=true
cached_pkg_prefix="pkg.path"
[expected]
kclvm_version="v0.3.0"
kcl_plugin_version="v0.2.0"
[package]
name = "test_add_deps"
edition = "0.0.1"
version = "0.0.1"

[dependencies]
pkg0 = { git = "test_url", tag = "test_tag" }
pkg1 = "oci_tag1"
pkg2 = { oci = "oci://ghcr.io/kcl-lang/helloworld", tag = "0.1.1" }
pkg3 = { path = "../pkg"}

[profile]
entries = ["main.k"]
12 changes: 6 additions & 6 deletions kclvm/config/src/vfs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ pub fn is_rel_pkgpath(pkgpath: &str) -> bool {

pub fn fix_import_path(root: &str, filepath: &str, import_path: &str) -> String {
// relpath: import .sub
// FixImportPath(root, "path/to/app/file.k", ".sub") => path.to.app.sub
// FixImportPath(root, "path/to/app/file.k", "..sub") => path.to.sub
// FixImportPath(root, "path/to/app/file.k", "...sub") => path.sub
// FixImportPath(root, "path/to/app/file.k", "....sub") => sub
// FixImportPath(root, "path/to/app/file.k", ".....sub") => ""
// fix_import_path(root, "path/to/app/file.k", ".sub") => path.to.app.sub
// fix_import_path(root, "path/to/app/file.k", "..sub") => path.to.sub
// fix_import_path(root, "path/to/app/file.k", "...sub") => path.sub
// fix_import_path(root, "path/to/app/file.k", "....sub") => sub
// fix_import_path(root, "path/to/app/file.k", ".....sub") => ""
//
// abspath: import path.to.sub
// FixImportPath(root, "path/to/app/file.k", "path.to.sub") => path.to.sub
// fix_import_path(root, "path/to/app/file.k", "path.to.sub") => path.to.sub

if !import_path.starts_with('.') {
return import_path.to_string();
Expand Down
Loading
Loading