From e052b3ce435b19df798c915ba79f0f6d3423380d Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 28 Aug 2026 16:07:12 +0300 Subject: [PATCH 1/3] chore: remove versions.lock generation - ENG-143 --- src/commands/bundle.rs | 3 - src/commands/version.rs | 77 ---- src/commands/version/dependency_graph.rs | 421 ------------------ src/commands/version/dependency_visitor.rs | 145 ------ .../version/has_call_to_function_visitor.rs | 60 --- src/commands/version/sdk_version.rs | 55 --- src/commands/version/utils.rs | 140 ------ src/commands/version/version_visitor.rs | 354 --------------- src/main.rs | 8 - 9 files changed, 1263 deletions(-) delete mode 100644 src/commands/version.rs delete mode 100644 src/commands/version/dependency_graph.rs delete mode 100644 src/commands/version/dependency_visitor.rs delete mode 100644 src/commands/version/has_call_to_function_visitor.rs delete mode 100644 src/commands/version/sdk_version.rs delete mode 100644 src/commands/version/utils.rs delete mode 100644 src/commands/version/version_visitor.rs diff --git a/src/commands/bundle.rs b/src/commands/bundle.rs index 1531be1..b94d52b 100644 --- a/src/commands/bundle.rs +++ b/src/commands/bundle.rs @@ -1,4 +1,3 @@ -use crate::commands::version::compute_versions; use crate::config::Flow; use crate::config::{self, SimplePlatform}; @@ -146,8 +145,6 @@ pub fn bundle(config_path: &str, is_rebundle: bool) -> Result<()> { .join("\n"), )?; - compute_versions(config_path)?; - if is_rebundle { info!("Rebundled all flows successfully"); } else { diff --git a/src/commands/version.rs b/src/commands/version.rs deleted file mode 100644 index 36573f6..0000000 --- a/src/commands/version.rs +++ /dev/null @@ -1,77 +0,0 @@ -use std::{collections::HashMap, path::PathBuf}; - -use anyhow::Result; -use darklua_core::Resources; - -use crate::{ - commands::version::{ - dependency_graph::{DepedencyGraph, Work}, - version_visitor::VersionFile, - }, - config, -}; - -pub mod dependency_graph; -mod dependency_visitor; -mod has_call_to_function_visitor; -pub mod sdk_version; -mod utils; -pub mod version_visitor; - -pub fn compute_version_for_flows( - resources: &Resources, - flow_paths: Vec, - version_file: VersionFile, -) -> Result> { - let graph = DepedencyGraph::new(); - let mut work = Work::new(graph, resources, flow_paths, version_file); - work.compute_dependency_graph() - .map_err(|e| anyhow::anyhow!("Failed to compute dependency graph: {:?}", e))?; - - Ok(work) -} - -pub fn compute_versions(config_path: &str) -> Result<()> { - let config = config::Config::from_file(config_path)?; - let resources = Resources::from_file_system(); - - let mut file_paths: Vec = Vec::new(); - - let mut path_to_alias = HashMap::new(); - - for platform in &config.platforms { - for flow in &platform.flows { - let input = PathBuf::from(&flow.path); - path_to_alias.insert(input.clone(), flow.alias.clone()); - file_paths.push(input.clone()); - } - } - - let mut config_path_dir_buf = PathBuf::from(config_path); - config_path_dir_buf.pop(); - let version_file: VersionFile = serde_json::from_str( - &std::fs::read_to_string(config_path_dir_buf.join("version_file.json")).map_err(|e| { - anyhow::anyhow!("Failed to read version file (version_file.json): {:?}", e) - })?, - )?; - - let work = compute_version_for_flows(&resources, file_paths, version_file)?; - - let versions = work.get_versions(); - - // finally, modify the versions HashMap to have Alias->Version instead of Path->Version - let mut alias_versions = HashMap::new(); - for (path, version) in &versions { - let alias = path_to_alias.get(path).unwrap(); - alias_versions.insert(alias.clone(), version.clone()); - } - - let mut config_path_dir_buf = PathBuf::from(config_path); - config_path_dir_buf.pop(); - std::fs::write( - config_path_dir_buf.join("versions.lock"), - serde_json::to_string(&alias_versions.clone())?, - )?; - - Ok(()) -} diff --git a/src/commands/version/dependency_graph.rs b/src/commands/version/dependency_graph.rs deleted file mode 100644 index 5c60bdd..0000000 --- a/src/commands/version/dependency_graph.rs +++ /dev/null @@ -1,421 +0,0 @@ -/* - * Part of this file is derived from the darklua project https://github.com/seaofvoices/darklua - * which is licensed under the MIT License. - * - * Original Copyright (c) 2020 jeparlefrancais - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -use std::{ - collections::HashMap, - path::{Path, PathBuf}, -}; - -use darklua_core::{ - process::{DefaultVisitor, NodeVisitor, ScopeVisitor}, - rules::{ContextBuilder, PathRequireMode, RequirePathLocator}, - Configuration, Resources, -}; -use petgraph::algo::toposort; -use tracing::warn; - -use crate::commands::version::{ - dependency_visitor::RequireDependencyProcessor, - sdk_version::SdkVersionOut, - utils::normalize_path, - version_visitor::{VersionFile, VersionResolver}, -}; - -#[derive(Default, Debug, Clone)] -pub enum State { - #[default] - NotProcessed, - Processing, - Processed, -} - -#[derive(Default, Debug, Clone)] -pub struct DependencyGraphNode { - depends_on: Vec, - /// it is a node/file that will be outputted (final flow) - is_top_node: bool, - sdk_version: SdkVersionOut, - state: State, - path: PathBuf, - block: Option, -} - -impl DependencyGraphNode { - pub fn new(is_top_node: bool, path: PathBuf) -> Self { - Self { - is_top_node, - path, - ..Default::default() - } - } - - pub fn create_top_node(path: PathBuf) -> Self { - DependencyGraphNode::new(true, path) - } - - pub fn create_node(path: PathBuf) -> Self { - DependencyGraphNode::new(false, path) - } - - pub fn is_done(&self) -> bool { - matches!(self.state, State::Processed) - } - - pub fn is_not_done(&self) -> bool { - !self.is_done() - } -} - -pub type DepedencyGraph = petgraph::stable_graph::StableDiGraph; - -pub struct Work<'a> { - pub graph: DepedencyGraph, - node_mapping: HashMap, - resources: &'a Resources, - configuration: Configuration, - top_node_paths: Vec, - version_file: VersionFile, -} - -impl<'a> Work<'a> { - pub fn new( - graph: DepedencyGraph, - resources: &'a Resources, - top_node_paths: Vec, - version_file: VersionFile, - ) -> Self { - Self { - graph, - node_mapping: HashMap::new(), - resources, - top_node_paths, - configuration: Configuration::default(), - version_file, - } - } - - /// Given a Vec, create nodes for each dependency and add them to the graph - /// Also, add everything to the node_mapping - /// - /// This does NOT add the edges itself - fn add_dependencies_to_graph( - &mut self, - deps: Vec, - ) -> Vec { - deps.iter() - .map(|dep| { - let index = match self.node_mapping.get(dep) { - // if we already have the dependency cached, just return the index - Some(index) => *index, - None => { - // if we don't have the dependency cached, create a new node and add it to the graph - let node = DependencyGraphNode::create_node(dep.clone()); - let index = self.graph.add_node(node); - self.node_mapping.insert(dep.clone(), index); - index - } - }; - - index - }) - .collect::>() - } - - fn advance_work( - &mut self, - node_index: petgraph::stable_graph::NodeIndex, - ) -> anyhow::Result { - let state = match self.graph.node_weight_mut(node_index) { - Some(node) => node.state.clone(), - None => return Err(anyhow::anyhow!("Node not found")), - }; - - match state { - State::NotProcessed => { - // traverse and collect all the deps - - // we don't care about the parser retaining lines or being dense, just go with the default one - let parser = darklua_core::Parser::default(); - - let mut block = parser - .parse( - self.resources - .get(&self.get_node(node_index).path) - .map_err(|e| { - anyhow::anyhow!( - "Failed to read file {}: {:?}", - self.get_node(node_index).path.display(), - e - ) - })? - .as_str(), - ) - .map_err(|e| { - anyhow::anyhow!( - "Failed to parse file {}: {:?}", - self.get_node(node_index).path.display(), - e - ) - })?; - - let deps = self.collect_dependencies(node_index, &mut block)?.clone(); - - self.add_dependencies_to_graph(deps.clone()); - - let node = self.get_node_mut(node_index); - node.state = State::Processing; - node.depends_on = deps.clone(); - node.block = Some(block); - Ok(State::Processing) - } - State::Processing => { - // first, process the node's own sdk versions - let mut version_visitor = VersionResolver::new(&self.version_file); - - let mut block = self - .graph - .node_weight_mut(node_index) - .unwrap() - .block - .as_mut() - .unwrap() - .clone(); - - ScopeVisitor::visit_block(&mut block, &mut version_visitor); - - // process the node's data based on the deps AFTER we've collected all the nodes and added all the edges - // otherwise, we'll get erroneous results - - self.get_node_mut(node_index).sdk_version = version_visitor.sdk_version(); - self.get_node_mut(node_index).state = State::Processed; - Ok(State::Processed) - } - State::Processed => { - // no work to do - Ok(state) - } - } - } - - fn get_node_mut( - &mut self, - node_index: petgraph::stable_graph::NodeIndex, - ) -> &mut DependencyGraphNode { - self.graph.node_weight_mut(node_index).unwrap() - } - - fn get_node(&self, node_index: petgraph::stable_graph::NodeIndex) -> &DependencyGraphNode { - self.graph.node_weight(node_index).unwrap() - } - - fn create_rule_context<'block, 'src>( - &self, - source: &Path, - original_code: &'src str, - ) -> ContextBuilder<'block, 'a, 'src> { - let builder = ContextBuilder::new(normalize_path(source), self.resources, original_code); - if let Some(project_location) = self.configuration.location() { - builder.with_project_location(project_location) - } else { - builder - } - } - - fn collect_dependencies( - &mut self, - node_index: petgraph::stable_graph::NodeIndex, - block: &mut darklua_core::nodes::Block, - ) -> anyhow::Result> { - // HARDCODED - let context = self - .create_rule_context(&self.graph.node_weight(node_index).unwrap().path, "") - .build(); - let mut path_require_mode = PathRequireMode::default(); - path_require_mode - .initialize(&context) - .map_err(|e| anyhow::anyhow!("Failed to initialize path require mode: {:?}", e))?; - - let require_path_locator = RequirePathLocator::new( - &path_require_mode, - &self.get_node(node_index).path, - self.resources, - ); - - let mut visitor = RequireDependencyProcessor::new( - self.get_node(node_index).path.clone(), - require_path_locator, - ); - - DefaultVisitor::visit_block(block, &mut visitor); - - if !visitor.errors().is_empty() { - return Err(anyhow::anyhow!( - "Failed to collect dependencies: {:?}", - visitor.errors() - )); - } - - Ok(visitor.deps().clone()) - } - - pub fn compute_dependency_graph(&mut self) -> Result<(), ()> { - // normalize path - // check to see if the nodes already exist in the graph - // if they do, don't do anything - // if they don't, create new nodes and add them to the graph - // also, recursively compute the dependency graph for them if they don't exist - for top_node_path in &self.top_node_paths { - let path = normalize_path(top_node_path); - if self.node_mapping.contains_key(&path) { - continue; - } - - let index = self - .graph - .add_node(DependencyGraphNode::create_top_node(path.clone())); - self.node_mapping.insert(path, index); - } - - let total_not_done = self - .graph - .node_weights() - .filter(|work_item| !work_item.is_done()) - .count(); - - if total_not_done == 0 { - return Ok(()); - } - - let mut done_count = 0; - - 'work_loop: loop { - let mut add_edges = Vec::new(); - - let node_indexes = match toposort(&self.graph, None) { - Ok(node_indexes) => node_indexes.clone(), - Err(err) => { - warn!("Error sorting graph, cycle detected: {:?}", err); - return Err(()); - } - }; - - for node_index in node_indexes { - if self.get_node(node_index).is_not_done() { - match self.advance_work(node_index) { - Ok(State::NotProcessed) => unreachable!(), - Ok(State::Processing) => { - for dep in self.get_node(node_index).depends_on.clone() { - if let Some(content_node_index) = self.node_mapping.get(&dep) { - add_edges.push((*content_node_index, node_index)); - } - } - } - Ok(State::Processed) => { - // we have to get the sdk version of the node - done_count += 1; - } - Err(err) => { - warn!("Error advancing work: {:?}", err); - return Err(()); - } - } - } - - if done_count == self.graph.node_count() { - for (from, to) in add_edges { - self.graph.add_edge(from, to, ()); - } - break 'work_loop; - } - } - - for (from, to) in add_edges { - self.graph.add_edge(from, to, ()); - } - } - - // now process the sdk versions based on the deps - let node_indexes = match toposort(&self.graph, None) { - Ok(node_indexes) => node_indexes.clone(), - Err(err) => { - warn!("Error sorting graph, cycle detected: {:?}", err); - return Err(()); - } - }; - - for node_index in node_indexes { - let sdk_version_of_deps = self - .get_node(node_index) - .depends_on - .iter() - .map(|dep| { - self.get_node(*self.node_mapping.get(dep).unwrap()) - .sdk_version - .clone() - }) - .collect::>(); - - self.get_node_mut(node_index).sdk_version = SdkVersionOut::sdk_version_intersection( - self.get_node(node_index).sdk_version.clone(), - sdk_version_of_deps - .iter() - .fold(SdkVersionOut::default(), |lhs, rhs| { - SdkVersionOut::sdk_version_intersection(lhs.clone(), rhs.clone()) - }), - ); - } - - Ok(()) - } - - pub fn get_versions(&self) -> HashMap { - self.graph - .node_weights() - .filter(|node| node.is_top_node) - .map(|node| (node.path.clone(), node.sdk_version.clone())) - .collect() - } - - #[allow(dead_code)] - pub fn dot_graph(&self) -> String { - use petgraph::dot::{Config, Dot}; - - let dot_string = format!( - "{:?}", - Dot::with_attr_getters( - &self.graph, - &[Config::EdgeNoLabel, Config::NodeNoLabel], - &|_, _| String::new(), - &|_, (_, node)| { - format!( - "label=\"{} - {}\"", - node.path.display(), - node.sdk_version.min_sdk_version - ) - }, - ) - ); - dot_string - } -} diff --git a/src/commands/version/dependency_visitor.rs b/src/commands/version/dependency_visitor.rs deleted file mode 100644 index 7cd61c2..0000000 --- a/src/commands/version/dependency_visitor.rs +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Part of this file is derived from the darklua project https://github.com/seaofvoices/darklua - * which is licensed under the MIT License. - * - * Original Copyright (c) 2020 jeparlefrancais - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -use std::path::{Path, PathBuf}; - -use bstr::ByteSlice; -use darklua_core::{ - nodes, - process::NodeProcessor, - rules::{PathLocator, RequirePathLocator}, -}; - -use crate::commands::version::utils::normalize_path_with_current_dir; - -#[derive(Debug)] -pub struct RequireDependencyProcessor<'a, 'b, 'c> { - depends_on: Vec, - current_file_path: PathBuf, - require_path_locator: RequirePathLocator<'a, 'b, 'c>, - errors: Vec, -} - -const REQUIRE_FUNCTION_IDENTIFIER: &str = "require"; - -/// This doesn't use an IdentifierTracker like the one from the DarkLua Project -/// As we assume people ONLY use the REQUIRE identifier directly to require files -fn is_require_call(call: &nodes::FunctionCall) -> bool { - if call.get_method().is_some() { - return false; - } - - match call.get_prefix() { - nodes::Prefix::Identifier(ident) => ident.get_name() == REQUIRE_FUNCTION_IDENTIFIER, - _ => false, - } -} - -fn convert_string_expression_to_path(string: &nodes::StringExpression) -> Option<&Path> { - string - .get_string_value() - .map(Path::new) - .or_else(|| bstr::BStr::new(string.get_value()).to_path().ok()) -} - -pub fn match_path_require_call(call: &nodes::FunctionCall) -> Option { - match call.get_arguments() { - nodes::Arguments::String(string) => convert_string_expression_to_path(string), - nodes::Arguments::Tuple(tuple) if tuple.len() == 1 => { - let expression = tuple.iter_values().next().unwrap(); - - match expression { - nodes::Expression::String(string) => convert_string_expression_to_path(string), - _ => None, - } - } - _ => None, - } - .map(normalize_path_with_current_dir) -} - -impl<'a, 'b, 'c> RequireDependencyProcessor<'a, 'b, 'c> { - pub fn new( - current_file_path: PathBuf, - require_path_locator: RequirePathLocator<'a, 'b, 'c>, - ) -> Self { - Self { - depends_on: Vec::new(), - current_file_path, - require_path_locator, - errors: Vec::new(), - } - } - fn require_call(&self, call: &nodes::FunctionCall) -> Option { - if is_require_call(call) { - match_path_require_call(call) - } else { - None - } - } - fn process(&mut self, call: &nodes::FunctionCall) -> Option<()> { - let literal_require_path = self.require_call(call)?; - - let require_path = match self - .require_path_locator - .find_require_path(literal_require_path, &self.current_file_path) - { - Ok(path) => path, - Err(err) => { - self.errors - .push(anyhow::anyhow!("Failed to find require path: {:?}", err)); - return None; - } - }; - - self.depends_on.push(require_path); - Some(()) - } - pub fn deps(&self) -> &Vec { - &self.depends_on - } - pub fn errors(&self) -> &Vec { - &self.errors - } -} - -impl<'a, 'b, 'c> NodeProcessor for RequireDependencyProcessor<'a, 'b, 'c> { - fn process_expression(&mut self, expression: &mut nodes::Expression) { - if let nodes::Expression::Call(call) = expression { - self.process(call); - } - } - - fn process_prefix_expression(&mut self, prefix: &mut nodes::Prefix) { - if let nodes::Prefix::Call(call) = prefix { - self.process(call); - } - } - - fn process_statement(&mut self, statement: &mut nodes::Statement) { - if let nodes::Statement::Call(call) = statement { - self.process(call); - } - } -} diff --git a/src/commands/version/has_call_to_function_visitor.rs b/src/commands/version/has_call_to_function_visitor.rs deleted file mode 100644 index 3c1e242..0000000 --- a/src/commands/version/has_call_to_function_visitor.rs +++ /dev/null @@ -1,60 +0,0 @@ -use darklua_core::process::NodeProcessor; -use darklua_core::{nodes, ScopedHashMap}; - -use crate::commands::version::utils::get_fqn; - -pub struct HasCallToFunctionVisitor<'a> { - function_name: String, - has_call_to_function_field: bool, - variable_scope: &'a ScopedHashMap>, -} - -impl<'a> HasCallToFunctionVisitor<'a> { - pub fn new( - function_name: String, - variable_scope: &'a ScopedHashMap>, - ) -> Self { - Self { - function_name, - has_call_to_function_field: false, - variable_scope, - } - } - - pub fn has_call_to_function(&self) -> bool { - self.has_call_to_function_field - } -} - -impl<'a> NodeProcessor for HasCallToFunctionVisitor<'a> { - fn process_expression(&mut self, expression: &mut nodes::Expression) { - if let nodes::Expression::Identifier(binary) = expression { - let name = binary.get_name().to_string(); - if let Some(Some(nodes::Expression::Call(call))) = self.variable_scope.get(&name) { - if let nodes::Prefix::Identifier(identifier) = call.get_prefix() { - if *identifier.get_name() == self.function_name { - self.has_call_to_function_field = true; - } - } - } - } - } - - fn process_function_call(&mut self, call: &mut nodes::FunctionCall) { - if call.get_method().is_some() { - return; - } - - let name = match call.get_prefix() { - nodes::Prefix::Identifier(identifier) => Some(identifier.get_name().to_string()), - nodes::Prefix::Field(field) => get_fqn(field), - _ => None, - }; - - if let Some(name) = name { - if name == self.function_name { - self.has_call_to_function_field = true; - } - } - } -} diff --git a/src/commands/version/sdk_version.rs b/src/commands/version/sdk_version.rs deleted file mode 100644 index 401415c..0000000 --- a/src/commands/version/sdk_version.rs +++ /dev/null @@ -1,55 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct SdkVersionOut { - pub min_sdk_version: u64, - #[serde(skip_serializing_if = "Option::is_none")] - pub max_sdk_version: Option, -} - -impl PartialEq for SdkVersionOut { - fn eq(&self, other: &Self) -> bool { - self.min_sdk_version == other.min_sdk_version - && self.max_sdk_version == other.max_sdk_version - } -} - -impl Eq for SdkVersionOut {} - -impl SdkVersionOut { - pub fn new(default_version: u64) -> Self { - Self { - min_sdk_version: default_version, - max_sdk_version: None, - } - } - - pub fn sdk_version_intersection(lhs: SdkVersionOut, rhs: SdkVersionOut) -> SdkVersionOut { - SdkVersionOut { - min_sdk_version: lhs.min_sdk_version.max(rhs.min_sdk_version), - max_sdk_version: Self::sdk_version_minimum_of_max( - lhs.max_sdk_version, - rhs.max_sdk_version, - ), - } - } - - pub fn sdk_version_minimum_of_max(lhs: Option, rhs: Option) -> Option { - match (lhs, rhs) { - (Some(lhs), Some(rhs)) => Some(lhs.min(rhs)), - (Some(lhs), None) => Some(lhs), - (None, Some(rhs)) => Some(rhs), - (None, None) => None, - } - } - - pub fn sdk_version_union(lhs: SdkVersionOut, rhs: SdkVersionOut) -> SdkVersionOut { - SdkVersionOut { - min_sdk_version: lhs.min_sdk_version.min(rhs.min_sdk_version), - max_sdk_version: match (lhs.max_sdk_version, rhs.max_sdk_version) { - (Some(lhs), Some(rhs)) => Some(lhs.max(rhs)), - _ => None, - }, - } - } -} diff --git a/src/commands/version/utils.rs b/src/commands/version/utils.rs deleted file mode 100644 index d4fb868..0000000 --- a/src/commands/version/utils.rs +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Part of this file is derived from the darklua project https://github.com/seaofvoices/darklua - * which is licensed under the MIT License. - * - * Original Copyright (c) 2020 jeparlefrancais - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -use std::{ - ffi::OsStr, - iter::FromIterator, - path::{Component, Path, PathBuf}, -}; - -#[inline] -fn current_dir() -> &'static OsStr { - OsStr::new(".") -} - -#[inline] -fn parent_dir() -> &'static OsStr { - OsStr::new("..") -} - -fn normalize(path: impl AsRef, keep_current_dir: bool) -> PathBuf { - let path = path.as_ref(); - - if path == Path::new("") { - return PathBuf::new(); - } - - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - vec![c.as_os_str()] - } else { - Vec::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => { - if keep_current_dir && ret.is_empty() { - ret.push(current_dir()); - } - } - Component::ParentDir => { - if let Some(last) = ret.last() { - let last = *last; - if last == current_dir() { - ret.pop(); - ret.push(parent_dir()); - } else if last != parent_dir() { - ret.pop(); - } else { - ret.push(parent_dir()); - } - } else { - ret.push(parent_dir()); - } - } - Component::Normal(c) => { - ret.push(c); - } - } - } - - if ret.is_empty() { - ret.push(OsStr::new(".")); - } - - PathBuf::from_iter(ret) -} - -pub fn normalize_path(path: impl AsRef) -> PathBuf { - normalize(path, false) -} - -pub fn normalize_path_with_current_dir(path: impl AsRef) -> PathBuf { - normalize(path, true) -} - -/// Get FULLY QUALIFIED NAME -/// Example: -/// -/// ``` -/// member.expression.inside.member.expression.call() -/// ``` -/// -/// This gets us -/// -/// ``` -/// member.expression.inside.member.expression.call -/// ``` -/// -/// It does NOT work for a any IndexExpression (a.k.a. computed member access) -/// -/// ``` -/// computed[member].expression.call() -/// ``` -/// -/// TODO: make it so that it also looks in the scope and resolves identifiers -pub fn get_fqn(field_expression: &darklua_core::nodes::FieldExpression) -> Option { - use darklua_core::nodes::*; - - match field_expression.get_prefix() { - Prefix::Identifier(prefix_ident) => { - // fqn.push_str(identifier.get_name()); - Some(format!( - "{}.{}", - prefix_ident.get_name(), - field_expression.get_field().get_name() - )) - } - Prefix::Field(field) => { - get_fqn(field).map(|fqn| format!("{}.{}", fqn, field_expression.get_field().get_name())) - } - _ => None, - } -} diff --git a/src/commands/version/version_visitor.rs b/src/commands/version/version_visitor.rs deleted file mode 100644 index 06a7f31..0000000 --- a/src/commands/version/version_visitor.rs +++ /dev/null @@ -1,354 +0,0 @@ -use std::collections::HashMap; - -use serde::{Deserialize, Serialize}; - -use darklua_core::process::{DefaultVisitor, NodeProcessor, NodeVisitor, Scope, ScopeVisitor}; -use darklua_core::{nodes, ScopedHashMap}; - -use crate::commands::version::has_call_to_function_visitor::HasCallToFunctionVisitor; -use crate::commands::version::sdk_version::SdkVersionOut; -use crate::commands::version::utils::get_fqn; - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct VersionFile { - /// What default MINIMUM version we should default to - pub default_version: Option, - /// The mappings for each function - pub function_mappings: HashMap, - /// Function name that lets us know how we figure out the current sdk version - /// Because sometimes we might have code as such: - /// - /// ``` - /// if fetch_sdk_version() > 25 then - /// use_function_min_sdk_version_26() - /// else - /// use_function_min_sdk_version_23() - /// endif - /// ``` - pub sdk_version_function: String, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FunctionMapping { - min_sdk_version: u64, - #[serde(default)] - max_sdk_version: Option, -} - -impl From<&FunctionMapping> for SdkVersionOut { - fn from(function_mapping: &FunctionMapping) -> Self { - Self { - min_sdk_version: function_mapping.min_sdk_version, - max_sdk_version: function_mapping.max_sdk_version, - } - } -} - -#[derive(Debug, Clone)] -pub struct VersionResolver<'a> { - variable_scope: ScopedHashMap>, - pub scope_stack: Vec, - scope_data: SdkVersionOut, - version_file: &'a VersionFile, -} - -impl<'a> VersionResolver<'a> { - pub fn new<'b: 'a>(version_file: &'b VersionFile) -> Self { - Self { - scope_stack: Vec::new(), - scope_data: SdkVersionOut::new(version_file.default_version.unwrap_or(1)), - version_file, - variable_scope: ScopedHashMap::default(), - } - } - - fn update_scope_data(lhs: &mut SdkVersionOut, rhs: &SdkVersionOut) { - let merged = SdkVersionOut::sdk_version_intersection(lhs.clone(), rhs.clone()); - *lhs = merged.clone(); - } - - fn update_last_scope_data(&mut self, sdk_version: SdkVersionOut) { - match self.scope_stack.last_mut() { - None => { - self.scope_data.min_sdk_version = sdk_version.min_sdk_version; - self.scope_data.max_sdk_version = sdk_version.max_sdk_version; - } - Some(scope_data) => { - Self::update_scope_data(scope_data, &sdk_version); - } - } - } - - pub fn sdk_version(&self) -> SdkVersionOut { - self.scope_data.clone() - } -} - -impl<'a> NodeProcessor for VersionResolver<'a> { - fn process_function_call(&mut self, call: &mut nodes::FunctionCall) { - if call.get_method().is_some() { - return; - } - - let name = match call.get_prefix() { - nodes::Prefix::Identifier(identifier) => Some(identifier.get_name().to_string()), - nodes::Prefix::Field(field) => get_fqn(field), - _ => None, - }; - - if let Some(name) = name { - let function_name = if name == "pcall" { - // if the name is pcall, that means our function should be the first argument to the pcall function - let args = call.get_arguments().clone(); - if args.is_empty() { - // we don't have any arguments, return, erroneous pcall - return; - } - match args.to_expressions().first().unwrap() { - nodes::Expression::Identifier(identifier) => identifier.get_name().to_string(), - nodes::Expression::Field(field) => match get_fqn(field) { - Some(fqn) => fqn, - None => return, - }, - _ => return, - } - } else { - name - }; - if let Some(function_mapping) = self.version_file.function_mappings.get(&function_name) - { - self.update_last_scope_data(function_mapping.into()); - } - } - } - - fn process_statement(&mut self, statement: &mut nodes::Statement) { - if let nodes::Statement::If(if_statement) = statement { - let branches = if_statement.get_branches(); - - match branches.len() { - 0 => unreachable!(), // at least 1 branch - 1 => { - let else_block = if_statement.get_else_block(); - - match else_block { - Some(else_block) => { - // if we have both the if and else branch, find the minimum of them 2 and return that - // FIRST, find if the conditional of the if branch contains the version_file.sdk_version_function call, otherwise we don't care - let mut has_call_to_function_visitor = HasCallToFunctionVisitor::new( - self.version_file.sdk_version_function.clone(), - &self.variable_scope, - ); - DefaultVisitor::visit_expression( - &mut branches[0].get_condition().clone(), - &mut has_call_to_function_visitor, - ); - - if !has_call_to_function_visitor.has_call_to_function() { - return; - } - - let mut cloned_if_block = branches[0].get_block().clone(); - let mut temp_visitor = VersionResolver::new(self.version_file); - ScopeVisitor::visit_block(&mut cloned_if_block, &mut temp_visitor); - let if_ver = temp_visitor.sdk_version(); - - let mut cloned_else_block = else_block.clone(); - let mut temp_visitor = VersionResolver::new(self.version_file); - ScopeVisitor::visit_block(&mut cloned_else_block, &mut temp_visitor); - let else_ver = temp_visitor.sdk_version(); - - // TODO: should we care about the max version here? usually when we have a check with the sdk_version_function call, - // we care solely about the min version (at least for now) - let min_sdk_version_full_version = - SdkVersionOut::sdk_version_union(if_ver, else_ver); - - self.update_last_scope_data(min_sdk_version_full_version); - - clear_if_statement(if_statement); - } - None => { - // if there is just one branch, the if branch, and no else branch, just return the version_file.default_sdk_version - let mut has_call_to_function_visitor = HasCallToFunctionVisitor::new( - self.version_file.sdk_version_function.clone(), - &self.variable_scope, - ); - DefaultVisitor::visit_expression( - &mut branches[0].get_condition().clone(), - &mut has_call_to_function_visitor, - ); - if !has_call_to_function_visitor.has_call_to_function() { - return; - } - - // self.update_last_scope_data(SdkVersionOut::new( - // self.version_file.default_version.unwrap_or(1), - // )); - - clear_if_statement(if_statement); - } - } - } - _ => { - // if we have elseifs, unrecognized, TODO, just leave it as it is for now - } - } - } - } -} - -fn clear_if_statement(if_statement: &mut nodes::IfStatement) { - // we will set every condition to true and every inner block to an empty block - if_statement - .mutate_branches() - .iter_mut() - .for_each(|branch| { - *branch.mutate_condition() = nodes::Expression::True(None); - *branch.mutate_block() = nodes::Block::new(vec![], None); - }); - if let Some(else_block) = if_statement.mutate_else_block() { - *else_block = nodes::Block::new(vec![], None); - } -} - -impl<'a> Scope for VersionResolver<'a> { - fn push(&mut self) { - self.scope_stack.push(SdkVersionOut::new( - self.version_file.default_version.unwrap_or(1), - )); - self.variable_scope.push(); - } - fn pop(&mut self) { - if let Some(curr_scope_data) = self.scope_stack.pop() { - match self.scope_stack.last_mut() { - None => { - // self.scope_data = *curr_scope_data; - self.scope_data.min_sdk_version = curr_scope_data.min_sdk_version; - self.scope_data.max_sdk_version = curr_scope_data.max_sdk_version; - } - Some(prev_scope_data) => { - prev_scope_data.min_sdk_version = prev_scope_data - .min_sdk_version - .max(curr_scope_data.min_sdk_version); - let merged = SdkVersionOut::sdk_version_intersection( - prev_scope_data.clone(), - curr_scope_data.clone(), - ); - self.update_last_scope_data(merged); - } - } - } - self.variable_scope.pop(); - } - fn insert(&mut self, _identifier: &mut String) {} - fn insert_local(&mut self, identifier: &mut String, value: Option<&mut nodes::Expression>) { - self.variable_scope - .insert(identifier.clone(), value.cloned()); - } - fn insert_local_function(&mut self, _function: &mut nodes::LocalFunctionStatement) {} - fn insert_self(&mut self) {} -} - -mod test { - use serde_json::json; - - use super::*; - - #[allow(dead_code)] - fn get_version_file() -> VersionFile { - serde_json::from_value(json!({ - "defaultVersion": 10, - "functionMappings": { - "get_sdk_version": { - "minSdkVersion": 13 - }, - "at_least_20": { - "minSdkVersion": 20 - }, - "less_than_20": { - "minSdkVersion": 16, - "maxSdkVersion": 19 - }, - "global_function_15": { - "minSdkVersion": 15 - } - }, - "sdkVersionFunction": "get_sdk_version" - })) - .unwrap() - } - - #[test] - fn test_version_visitor_with_if_else_statement() { - let file = r#" -function main() - local x = 33 - for i = 1, 10 do - local sdk_version = get_sdk_version() - if sdk_version >= 20 then - at_least_20() - else - less_than_20() - end - end -end - "#; - - let parser = darklua_core::Parser::default(); - let mut block = parser.parse(file).unwrap(); - - let version_file = get_version_file(); - let mut version_visitor = VersionResolver::new(&version_file); - ScopeVisitor::visit_block(&mut block, &mut version_visitor); - - assert!(version_visitor.sdk_version().min_sdk_version == 16) - } - - #[test] - fn test_version_visitor_with_if_statement() { - let file = r#" -function main() - local x = 33 - for i = 1, 10 do - local sdk_version = get_sdk_version() - if sdk_version >= 20 then - at_least_20() - end - end -end - "#; - - let parser = darklua_core::Parser::default(); - let mut block = parser.parse(file).unwrap(); - - let version_file = get_version_file(); - let mut version_visitor = VersionResolver::new(&version_file); - ScopeVisitor::visit_block(&mut block, &mut version_visitor); - - // check for the get_sdk_version min version - assert!(version_visitor.sdk_version().min_sdk_version == 13) - } - - #[test] - fn test_version_visitor_with_pcall() { - let file = r#" -function main() - local x = 33 - for i = 1, 10 do - local my_test_call = pcall(global_function_15) - end -end - "#; - - let parser = darklua_core::Parser::default(); - let mut block = parser.parse(file).unwrap(); - - let version_file = get_version_file(); - let mut version_visitor = VersionResolver::new(&version_file); - ScopeVisitor::visit_block(&mut block, &mut version_visitor); - - assert!(version_visitor.sdk_version().min_sdk_version == 15) - } -} diff --git a/src/main.rs b/src/main.rs index 5085c7b..ebe9f7e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ mod commands { pub mod bundle; pub mod generate_completions; pub mod serve; - pub mod version; } use commands::analyze::analyze; @@ -16,8 +15,6 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use tracing::Level; -use crate::commands::version::compute_versions; - #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { @@ -53,10 +50,6 @@ enum Commands { #[arg(short, long, default_value_t = 8080)] port: u16, }, - - /// Compute versions for all flows - #[command(name = "compute-versions")] - ComputeVersions, } async fn run() -> Result<(), Box> { @@ -66,7 +59,6 @@ async fn run() -> Result<(), Box> { Commands::Analyze => analyze(&cli.config)?, Commands::GenerateCompletions { shell } => generate_completions(shell)?, Commands::Serve { rebundle, port } => serve(&cli.config, *rebundle, *port).await?, - Commands::ComputeVersions => compute_versions(&cli.config)?, } Ok(()) } From 5fa7ae2ad40ae59e568aec30795a436cf1ce638d Mon Sep 17 00:00:00 2001 From: Adrian Date: Fri, 28 Aug 2026 16:18:44 +0300 Subject: [PATCH 2/3] chore: cleanup unused deps --- Cargo.toml | 6 +----- src/config.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6c41b1b..1bebcca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ serde = { version = "1.0", features = ["derive"] } toml = "0.8.20" which = "7.0.2" darklua = { version = "0.17.4", git = "https://github.com/OpacityLabs/darklua" } -serde_derive = "1.0.217" clap_complete = "4.5.46" axum = "0.8.3" tower-http = { version = "0.6.2", features = ["trace"] } @@ -23,7 +22,4 @@ tokio = { version = "1.44.2", features = ["full"] } uuid = { version = "1.16.0", features = ["v4"] } chrono = "0.4.40" tracing-subscriber = "0.3.19" -notify = "6.1.1" -sha2 = "0.10.9" -petgraph = "0.8.3" -bstr = "1.12.1" \ No newline at end of file +sha2 = "0.10.9" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index e74fa00..bdd0e55 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -use serde_derive::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] pub struct Config { From e1a272211421d5c782963d7c9e6b62b117b0002b Mon Sep 17 00:00:00 2001 From: Adrian Date: Sat, 29 Aug 2026 09:21:19 +0300 Subject: [PATCH 3/3] feat: turn on rebundle by default, add --no-rebundle flag - ENG-144 --- src/main.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index ebe9f7e..3c1c2f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use commands::serve::serve; use anyhow::Result; use clap::{Parser, Subcommand}; -use tracing::Level; +use tracing::{warn, Level}; #[derive(Parser)] #[command(author, version, about, long_about = None)] @@ -40,12 +40,16 @@ enum Commands { shell: String, }, - /// Serve Lua flows over HTTP (and rebundle only the requested flow, if rebundle is enabled) + /// Serve Lua flows over HTTP, rebundling the requested flow on each request Serve { - /// Rebundle only the requested flow, if rebundle is enabled - #[arg(short, long)] + /// Deprecated: rebundling is on by default, this flag is no longer needed + #[arg(short, long, conflicts_with = "no_rebundle")] rebundle: bool, + /// Serve the already bundled flows without rebundling them + #[arg(short, long)] + no_rebundle: bool, + /// Port to serve on #[arg(short, long, default_value_t = 8080)] port: u16, @@ -58,7 +62,16 @@ async fn run() -> Result<(), Box> { Commands::Bundle => bundle(&cli.config, false)?, Commands::Analyze => analyze(&cli.config)?, Commands::GenerateCompletions { shell } => generate_completions(shell)?, - Commands::Serve { rebundle, port } => serve(&cli.config, *rebundle, *port).await?, + Commands::Serve { + rebundle, + no_rebundle, + port, + } => { + if *rebundle { + warn!("--rebundle is deprecated and no longer needed: rebundling is enabled by default. Use --no-rebundle to turn it off."); + } + serve(&cli.config, !*no_rebundle, *port).await? + } } Ok(()) }