Skip to content

Commit e542584

Browse files
mmahroussfda-odoo
authored andcommitted
[IMP] server: config add base var and ver detection
1 parent d0168cc commit e542584

File tree

2 files changed

+183
-30
lines changed

2 files changed

+183
-30
lines changed

server/src/core/config.rs

Lines changed: 72 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -515,9 +515,13 @@ pub struct ConfigEntryRaw {
515515
#[serde(default, serialize_with = "serialize_option_as_default")]
516516
add_workspace_addon_path: Option<Sourced<bool>>,
517517

518+
518519
#[serde(default, rename(serialize = "$version", deserialize = "$version"), serialize_with = "serialize_option_as_default")]
519520
version: Option<Sourced<String>>,
520521

522+
#[serde(default, rename(serialize = "$base", deserialize = "$base"), serialize_with = "serialize_option_as_default")]
523+
base: Option<Sourced<String>>,
524+
521525
#[serde(default)]
522526
diagnostic_settings: HashMap<DiagnosticCode, Sourced<DiagnosticSetting>>,
523527

@@ -543,6 +547,7 @@ impl Default for ConfigEntryRaw {
543547
auto_refresh_delay: None,
544548
add_workspace_addon_path: None,
545549
version: None,
550+
base: None,
546551
diagnostic_settings: Default::default(),
547552
abstract_: false,
548553
}
@@ -653,10 +658,13 @@ fn process_paths(
653658
ws_folders: &HashMap<String, String>,
654659
workspace_name: Option<&String>,
655660
){
656-
let var_map: HashMap<String, String> = match entry.version.clone() {
657-
Some(v) => HashMap::from([(S!("version"), v.value().clone())]),
658-
None => HashMap::new(),
659-
};
661+
let mut var_map: HashMap<String, String> = HashMap::new();
662+
if let Some(v) = entry.version.clone() {
663+
var_map.insert(S!("version"), v.value().clone());
664+
}
665+
if let Some(b) = entry.base.clone() {
666+
var_map.insert(S!("base"), b.value().clone());
667+
}
660668
entry.odoo_path = entry.odoo_path.as_ref()
661669
.and_then(|p| fill_or_canonicalize(p, ws_folders, workspace_name, &is_odoo_path, var_map.clone()));
662670

@@ -788,6 +796,7 @@ fn apply_merge(child: &ConfigEntryRaw, parent: &ConfigEntryRaw) -> ConfigEntryRa
788796
let auto_refresh_delay = child.auto_refresh_delay.clone().or(parent.auto_refresh_delay.clone());
789797
let add_workspace_addon_path = child.add_workspace_addon_path.clone().or(parent.add_workspace_addon_path.clone());
790798
let version = child.version.clone().or(parent.version.clone());
799+
let base = child.base.clone().or(parent.base.clone());
791800
let diagnostic_settings = merge_sourced_diagnostic_setting_map(&child.diagnostic_settings, &parent.diagnostic_settings);
792801

793802
ConfigEntryRaw {
@@ -806,6 +815,7 @@ fn apply_merge(child: &ConfigEntryRaw, parent: &ConfigEntryRaw) -> ConfigEntryRa
806815
auto_refresh_delay,
807816
add_workspace_addon_path,
808817
version,
818+
base,
809819
diagnostic_settings: diagnostic_settings,
810820
..Default::default()
811821
}
@@ -939,42 +949,56 @@ fn load_config_from_workspace(
939949
break;
940950
}
941951
}
942-
let mut merged_config = process_config(merged_config, ws_folders, Some(workspace_name))?;
943-
944-
for entry in merged_config.values_mut() {
945-
if entry.abstract_ { continue; }
946-
if (matches!(entry.add_workspace_addon_path.as_ref().map(|a| a.value), Some(true)) || entry.addons_paths.is_none()) && is_addon_path(workspace_path) {
947-
let addon_path = Sourced { value: workspace_path.clone(), sources: HashSet::from([S!(format!("$workspaceFolder:{workspace_name}"))]), ..Default::default()};
948-
match entry.addons_paths {
949-
Some(ref mut paths) => paths.push(addon_path),
950-
None => entry.addons_paths = Some(vec![addon_path]),
952+
let mut new_configs = vec![];
953+
for config in merged_config.values_mut() {
954+
// Make $base always an absolute path
955+
if let Some(base_var) = config.base.clone() {
956+
let base_path = PathBuf::from(base_var.value());
957+
958+
// If $base ends with ${detectVersion}, match workspace path and set $version using path components
959+
if base_path.components().last().map(|c| c.as_os_str().to_string_lossy() == "${detectVersion}").unwrap_or(false) {
960+
let Some(base_prefix_pb) = base_path.parent().map(PathBuf::from) else {
961+
return Err(S!("$base must be a valid path with a parent directory"));
962+
};
963+
let abs_base = match base_prefix_pb.canonicalize() {
964+
Ok(p) => p,
965+
Err(e) => return Err(S!(format!("Failed to canonicalize base path: {} ({})", base_prefix_pb.display(), e))),
966+
};
967+
let base_prefix_pb = PathBuf::from(abs_base.sanitize());
968+
let ws_path_pb: PathBuf = PathBuf::from(workspace_path);
969+
let base_prefix_components: Vec<_> = base_prefix_pb.components().collect();
970+
let ws_path_components: Vec<_> = ws_path_pb.components().collect();
971+
if ws_path_components.len() > base_prefix_components.len()
972+
&& ws_path_components[..base_prefix_components.len()] == base_prefix_components[..] {
973+
let version_component = ws_path_components[base_prefix_components.len()];
974+
let version_str = version_component.as_os_str().to_string_lossy().to_string();
975+
if !version_str.is_empty() {
976+
config.version = Some(Sourced { value: version_str.clone(), ..Default::default() });
977+
let mut resolved_base = base_prefix_pb.clone();
978+
resolved_base.push(&version_str);
979+
config.base.as_mut().map(|b| b.value = resolved_base.sanitize());
980+
} else {
981+
return Err(S!("Could not extract version from workspace path using $base"));
982+
}
983+
} else {
984+
return Err(S!("$base does not match the current workspace folder"));
985+
}
951986
}
952987
}
953-
}
954-
Ok(merged_config)
955-
}
956-
957-
fn process_config(
958-
mut config_map: HashMap<String, ConfigEntryRaw>,
959-
ws_folders: &HashMap<String, String>,
960-
workspace_name: Option<&String>,
961-
) -> Result<HashMap<String, ConfigEntryRaw>, String> {
962-
apply_extends(&mut config_map)?;
963-
let mut new_configs = vec![];
964-
for config in config_map.values_mut() {
988+
// Also handle legacy $version with ${splitVersion}
965989
let Some(version_var) = config.version.clone() else {
966990
continue;
967991
};
968992
let version_path = PathBuf::from(version_var.value());
969-
if version_path.components().last().map(|c| c.as_os_str().to_string_lossy() == "${detectVersion}").unwrap_or(false) {
993+
if version_path.components().last().map(|c| c.as_os_str().to_string_lossy() == "${splitVersion}").unwrap_or(false) {
970994
config.abstract_ = true;
971995
let Some(parent_dir) = version_path.parent() else {
972996
continue;
973997
};
974998
let Some(parent_dir) = fill_or_canonicalize(
975999
&{Sourced { value: parent_dir.sanitize(), sources: version_var.sources.clone(), ..Default::default() }},
9761000
ws_folders,
977-
workspace_name,
1001+
Some(workspace_name),
9781002
&|p| PathBuf::from(p).is_dir(),
9791003
HashMap::new(),
9801004
) else {
@@ -999,9 +1023,29 @@ fn process_config(
9991023
}
10001024
}
10011025
for new_entry in new_configs {
1002-
config_map.insert(new_entry.name.clone(), new_entry);
1026+
merged_config.insert(new_entry.name.clone(), new_entry);
1027+
}
1028+
let mut merged_config = process_config(merged_config, ws_folders, Some(workspace_name))?;
1029+
1030+
for entry in merged_config.values_mut() {
1031+
if entry.abstract_ { continue; }
1032+
if (matches!(entry.add_workspace_addon_path.as_ref().map(|a| a.value), Some(true)) || entry.addons_paths.is_none()) && is_addon_path(workspace_path) {
1033+
let addon_path = Sourced { value: workspace_path.clone(), sources: HashSet::from([S!(format!("$workspaceFolder:{workspace_name}"))]), ..Default::default()};
1034+
match entry.addons_paths {
1035+
Some(ref mut paths) => paths.push(addon_path),
1036+
None => entry.addons_paths = Some(vec![addon_path]),
1037+
}
1038+
}
10031039
}
1040+
Ok(merged_config)
1041+
}
10041042

1043+
fn process_config(
1044+
mut config_map: HashMap<String, ConfigEntryRaw>,
1045+
ws_folders: &HashMap<String, String>,
1046+
workspace_name: Option<&String>,
1047+
) -> Result<HashMap<String, ConfigEntryRaw>, String> {
1048+
apply_extends(&mut config_map)?;
10051049
// Process vars
10061050
config_map.values_mut()
10071051
.for_each(|entry| {

server/tests/config_tests.rs

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,11 +1634,11 @@ fn test_detect_version_variable_creates_profiles_for_each_version() {
16341634
addon18.create_dir_all().unwrap();
16351635
addon18.child("__manifest__.py").touch().unwrap();
16361636

1637-
// Write odools.toml in ws1 with $version = "${workspaceFolder}${detectVersion}"
1637+
// Write odools.toml in ws1 with $version = "${workspaceFolder}${splitVersion}"
16381638
let toml_content = r#"
16391639
[[config]]
16401640
name = "root"
1641-
"$version" = "${workspaceFolder}/${detectVersion}"
1641+
"$version" = "${workspaceFolder}/${splitVersion}"
16421642
addons_paths = [
16431643
"./${version}"
16441644
]
@@ -1739,4 +1739,113 @@ fn test_config_file_path_nonexistent_errors() {
17391739
let config_path = non_existent.path().sanitize();
17401740
let result = get_configuration(&ws_folders, &Some(config_path));
17411741
assert!(result.is_err(), "Expected error when config file path does not exist");
1742+
}
1743+
1744+
#[test]
1745+
fn test_base_and_version_resolve_for_workspace_subpaths() {
1746+
let temp = TempDir::new().unwrap();
1747+
let vdir = temp.child("17.0");
1748+
vdir.create_dir_all().unwrap();
1749+
let odoo_dir = vdir.child("odoo");
1750+
odoo_dir.create_dir_all().unwrap();
1751+
odoo_dir.child("odoo").child("release.py").touch().unwrap();
1752+
let addon_dir = vdir.child("addon-path");
1753+
addon_dir.create_dir_all().unwrap();
1754+
addon_dir.child("mod1").create_dir_all().unwrap();
1755+
addon_dir.child("mod1").child("__manifest__.py").touch().unwrap();
1756+
1757+
// Write odools.toml in /temp with $base and $version logic
1758+
let toml_content = format!(r#"
1759+
[[config]]
1760+
name = "default"
1761+
"$base" = "{}/${{detectVersion}}"
1762+
odoo_path = "${{base}}/odoo"
1763+
addons_paths = [ "${{base}}/addon-path" ]
1764+
"#, temp.path().sanitize());
1765+
temp.child("odools.toml").write_str(&toml_content).unwrap();
1766+
1767+
// Test with workspace at /temp/17.0
1768+
let mut ws_folders = HashMap::new();
1769+
ws_folders.insert(S!("ws1"), vdir.path().sanitize().to_string());
1770+
let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap();
1771+
let config = config_map.get("default").unwrap();
1772+
assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize());
1773+
assert!(config.addons_paths.contains(&addon_dir.path().sanitize()));
1774+
1775+
// Test with workspace at /temp/17.0/odoo
1776+
let mut ws_folders = HashMap::new();
1777+
ws_folders.insert(S!("ws2"), odoo_dir.path().sanitize().to_string());
1778+
let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap();
1779+
let config = config_map.get("default").unwrap();
1780+
assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize());
1781+
assert!(config.addons_paths.contains(&addon_dir.path().sanitize()));
1782+
1783+
// Test with workspace at /temp/17.0/addon-path
1784+
let mut ws_folders = HashMap::new();
1785+
ws_folders.insert(S!("ws3"), addon_dir.path().sanitize().to_string());
1786+
let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap();
1787+
let config = config_map.get("default").unwrap();
1788+
assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize());
1789+
assert!(config.addons_paths.contains(&addon_dir.path().sanitize()));
1790+
1791+
// --- Scenario: use /temp/${version}/odoo and /temp/${version}/addon-path instead of ${base} ---
1792+
let toml_content_version = format!(r#"
1793+
[[config]]
1794+
name = "default"
1795+
"$base" = "{0}/${{detectVersion}}"
1796+
odoo_path = "{0}/${{version}}/odoo"
1797+
addons_paths = [ "{0}/${{version}}/addon-path" ]
1798+
"#, temp.path().sanitize());
1799+
temp.child("odools.toml").write_str(&toml_content_version).unwrap();
1800+
1801+
// Test with workspace at /temp/17.0
1802+
let mut ws_folders = HashMap::new();
1803+
ws_folders.insert(S!("ws1"), vdir.path().sanitize().to_string());
1804+
let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap();
1805+
let config = config_map.get("default").unwrap();
1806+
assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize());
1807+
assert!(config.addons_paths.contains(&addon_dir.path().sanitize()));
1808+
1809+
// Test with workspace at /temp/17.0/odoo
1810+
let mut ws_folders = HashMap::new();
1811+
ws_folders.insert(S!("ws2"), odoo_dir.path().sanitize().to_string());
1812+
let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap();
1813+
let config = config_map.get("default").unwrap();
1814+
assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize());
1815+
assert!(config.addons_paths.contains(&addon_dir.path().sanitize()));
1816+
1817+
// Test with workspace at /temp/17.0/addon-path
1818+
let mut ws_folders = HashMap::new();
1819+
ws_folders.insert(S!("ws3"), addon_dir.path().sanitize().to_string());
1820+
let (config_map, _config_file) = get_configuration(&ws_folders, &None).unwrap();
1821+
let config = config_map.get("default").unwrap();
1822+
assert_eq!(config.odoo_path.as_ref().unwrap(), &odoo_dir.path().sanitize());
1823+
assert!(config.addons_paths.contains(&addon_dir.path().sanitize()));
1824+
// --- Crash scenario: $base is an absolute path (should error) ---
1825+
let toml_content_abs = format!(r#"
1826+
[[config]]
1827+
name = "default"
1828+
"$base" = "/not/a/real/path/${{detectVersion}}"
1829+
odoo_path = "${{base}}/odoo"
1830+
addons_paths = [ "${{base}}/addon-path" ]
1831+
"#);
1832+
temp.child("odools.toml").write_str(&toml_content_abs).unwrap();
1833+
let mut ws_folders = HashMap::new();
1834+
ws_folders.insert(S!("ws_abs"), vdir.path().sanitize().to_string());
1835+
let result = get_configuration(&ws_folders, &None);
1836+
assert!(result.is_err(), "Expected error when $base is an absolute path");
1837+
1838+
// --- Crash scenario: $base is not a valid path (should error) ---
1839+
let toml_content_invalid = r#"
1840+
[[config]]
1841+
name = "default"
1842+
"$base" = "invalid_path/${detectVersion}"
1843+
odoo_path = "${base}/odoo"
1844+
addons_paths = [ "${base}/addon-path" ]
1845+
"#;
1846+
temp.child("odools.toml").write_str(toml_content_invalid).unwrap();
1847+
let mut ws_folders = HashMap::new();
1848+
ws_folders.insert(S!("ws_invalid"), vdir.path().sanitize().to_string());
1849+
let result = get_configuration(&ws_folders, &None);
1850+
assert!(result.is_err(), "Expected error when $base is not a valid path");
17421851
}

0 commit comments

Comments
 (0)