Repository: sagiegurari/cargo-make (rust)

Issue:
`--skip-init-end-tasks` doesn't seem to work
### Describe The Bug
With `tasks.init` defined, even if we call `cargo make --skip-init-end-tasks empty`, the init task will be called.

I am not sure if I am understanding the `--skip-init-end-tasks` argument incorrectly, or there is a bug. If it's a misunderstanding, I believe we should change the documentation for the `--skip-init-end-tasks` argument: "If set, init and end tasks are skipped". If it's a bug, we need a fix.

### To Reproduce

On Linux with bash:
```bash
cargo init hello-world && cd hello-world && echo "tasks.init.script = \"echo init\"" > Makefile.toml && cargo make --skip-init-end-tasks empty
```

The actual output is:
```
$ cargo init hello-world && cd hello-world && echo "tasks.init.script = \"echo init\"" > Makefile.toml && cargo make --skip-init-end-tasks empty
    Creating binary (application) package
note: see more `Cargo.toml` keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[cargo-make] INFO - cargo make 0.37.13
[cargo-make] INFO - Calling cargo metadata to extract project info
[cargo-make] INFO - Cargo metadata done
[cargo-make] INFO - Project: hello-world
[cargo-make] INFO - Build File: Makefile.toml
[cargo-make] INFO - Task: empty
[cargo-make] INFO - Profile: development
[cargo-make] INFO - Running Task: init
init
[cargo-make] INFO - Build Done in 0.28 seconds.
```

I guess we are not supposed to see the "Running Task: init" log and the "init" echo?

### Error Stack

N/A

### Code Sample

```toml
tasks.init.script = "echo init"
```

This problem is important to me, as I plan to use the init task to add "-D warning" to the `RUSTFLAGS` environement variable for the CI environment. However, ff `--skip-init-end-tasks` doesn't work, the init task will be run another time when a new cargo-make process is forked due to `tasks.<task_id>.run_task.fork` being `true`. As a result, 2 duplicate "-D warnings" will be added to `RUSTFLAGS`, and cause recompilation.

EDIT: move the `--skip-init-end-tasks` argument before the empty target in To Reproduce section, and update the output.

Pull request description:
Fix --skip-init-end-tasks argument 
Fix #1108.

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 70d63c34..05646c9d 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
 ## CHANGELOG
 
+### v0.37.14
+
+* Fix: fix the --skip-init-end-tasks argument #1108
+
 ### v0.37.13 (2024-07-07)
 
 * Enhancement: New condition_script_runner_args attribute #1081
diff --git a/src/lib/cli.rs b/src/lib/cli.rs
index d0e74358..9c664851 100644
--- a/src/lib/cli.rs
+++ b/src/lib/cli.rs
@@ -149,14 +149,18 @@ fn run(cli_args: CliArgs, global_config: &GlobalConfig) {
             &env_info.crate_info,
         );
     } else if cli_args.print_only {
-        cli_commands::print_steps::print(
+        if let Err(e) = cli_commands::print_steps::print(
+            &mut std::io::stdout(),
             &config,
             &task,
             &cli_args.output_format,
             cli_args.disable_workspace,
             cli_args.skip_tasks_pattern,
             &env_info.crate_info,
-        );
+            cli_args.skip_init_end_tasks,
+        ) {
+            error!("Failed to print steps: {}", e);
+        }
     } else {
         runner::run(
             config,
diff --git a/src/lib/cli_commands/diff_steps.rs b/src/lib/cli_commands/diff_steps.rs
index 01991bc9..1ae02f9d 100644
--- a/src/lib/cli_commands/diff_steps.rs
+++ b/src/lib/cli_commands/diff_steps.rs
@@ -8,7 +8,7 @@
 mod diff_steps_test;
 
 use crate::command;
-use crate::execution_plan::create as create_execution_plan;
+use crate::execution_plan::ExecutionPlanBuilder;
 use crate::io::{create_file, delete_file};
 use crate::types::{CliArgs, Config, CrateInfo, ExecutionPlan};
 use regex::Regex;
@@ -40,25 +40,23 @@ pub(crate) fn run(
         None => None,
     };
 
-    let internal_execution_plan = create_execution_plan(
-        internal_config,
-        &task,
-        crateinfo,
-        cli_args.disable_workspace,
-        true,
-        false,
-        &skip_tasks_pattern,
-    );
+    let internal_execution_plan = ExecutionPlanBuilder {
+        crate_info: Some(crateinfo),
+        disable_workspace: cli_args.disable_workspace,
+        allow_private: true,
+        skip_tasks_pattern: skip_tasks_pattern.as_ref(),
+        ..ExecutionPlanBuilder::new(internal_config, &task)
+    }
+    .build();
 
-    let external_execution_plan = create_execution_plan(
-        external_config,
-        &task,
-        crateinfo,
-        cli_args.disable_workspace,
-        true,
-        false,
-        &skip_tasks_pattern,
-    );
+    let external_execution_plan = ExecutionPlanBuilder {
+        crate_info: Some(crateinfo),
+        disable_workspace: cli_args.disable_workspace,
+        allow_private: true,
+        skip_tasks_pattern: skip_tasks_pattern.as_ref(),
+        ..ExecutionPlanBuilder::new(external_config, &task)
+    }
+    .build();
 
     let internal_file = create_file(
         &move |file: &mut File| write_as_string(&internal_execution_plan, &file),
diff --git a/src/lib/cli_commands/print_steps.rs b/src/lib/cli_commands/print_steps.rs
index cdc5a2ff..e2911942 100644
--- a/src/lib/cli_commands/print_steps.rs
+++ b/src/lib/cli_commands/print_steps.rs
@@ -7,7 +7,9 @@
 #[path = "print_steps_test.rs"]
 mod print_steps_test;
 
-use crate::execution_plan::create as create_execution_plan;
+use std::io;
+
+use crate::execution_plan::ExecutionPlanBuilder;
 use crate::types::{Config, CrateInfo, ExecutionPlan};
 use regex::Regex;
 
@@ -42,7 +44,10 @@ fn get_format_type(output_format: &str) -> PrintFormat {
     }
 }
 
-fn print_short_description(execution_plan: &ExecutionPlan) {
+fn print_short_description(
+    output_buffer: &mut impl io::Write,
+    execution_plan: &ExecutionPlan,
+) -> io::Result<()> {
     let mut counter = 1;
     for step in &execution_plan.steps {
         let task = &step.config;
@@ -50,25 +55,35 @@ fn print_short_description(execution_plan: &ExecutionPlan) {
             Some(value) => value,
             None => "no description",
         };
-        println!("{}. {} - {}", counter, &step.name, &description);
+        writeln!(
+            output_buffer,
+            "{}. {} - {}",
+            counter, &step.name, &description
+        )?;
 
         counter = counter + 1;
     }
+    Ok(())
 }
 
-fn print_default(execution_plan: &ExecutionPlan) {
-    println!("{:#?}", &execution_plan);
+fn print_default(
+    output_buffer: &mut impl io::Write,
+    execution_plan: &ExecutionPlan,
+) -> io::Result<()> {
+    writeln!(output_buffer, "{:#?}", &execution_plan)
 }
 
 /// Only prints the execution plan
 pub(crate) fn print(
+    output_buffer: &mut impl io::Write,
     config: &Config,
     task: &str,
     output_format: &str,
     disable_workspace: bool,
     skip_tasks_pattern: Option<String>,
     crateinfo: &CrateInfo,
-) {
+    skip_init_end_tasks: bool,
+) -> io::Result<()> {
     let skip_tasks_pattern_regex = match skip_tasks_pattern {
         Some(ref pattern) => match Regex::new(pattern) {
             Ok(reg) => Some(reg),
@@ -80,21 +95,21 @@ pub(crate) fn print(
         None => None,
     };
 
-    let execution_plan = create_execution_plan(
-        &config,
-        &task,
-        crateinfo,
+    let execution_plan = ExecutionPlanBuilder {
+        crate_info: Some(crateinfo),
         disable_workspace,
-        false,
-        false,
-        &skip_tasks_pattern_regex,
-    );
+        skip_tasks_pattern: skip_tasks_pattern_regex.as_ref(),
+        skip_init_end_tasks,
+        ..ExecutionPlanBuilder::new(&config, &task)
+    }
+    .build();
     debug!("Created execution plan: {:#?}", &execution_plan);
 
     let print_format = get_format_type(&output_format);
 
     match print_format {
-        PrintFormat::ShortDescription => print_short_description(&execution_plan),
-        PrintFormat::Default => print_default(&execution_plan),
+        PrintFormat::ShortDescription => print_short_description(output_buffer, &execution_plan)?,
+        PrintFormat::Default => print_default(output_buffer, &execution_plan)?,
     };
+    Ok(())
 }
diff --git a/src/lib/execution_plan.rs b/src/lib/execution_plan.rs
index 6c8ba795..2bc58b0c 100644
--- a/src/lib/execution_plan.rs
+++ b/src/lib/execution_plan.rs
@@ -336,7 +336,7 @@ fn create_for_step(
     task_names: &mut HashSet<String>,
     root: bool,
     allow_private: bool,
-    skip_tasks_pattern: &Option<Regex>,
+    skip_tasks_pattern: Option<&Regex>,
 ) {
     if let Some(skip_tasks_pattern_regex) = skip_tasks_pattern {
         if skip_tasks_pattern_regex.is_match(&task.name) {
@@ -432,68 +432,99 @@ fn add_predefined_step(config: &Config, task: &str, steps: &mut Vec<Step>) {
     }
 }
 
-/// Creates the full execution plan
-pub(crate) fn create(
-    config: &Config,
-    task: &str,
-    crate_info: &CrateInfo,
-    disable_workspace: bool,
-    allow_private: bool,
-    sub_flow: bool,
-    skip_tasks_pattern: &Option<Regex>,
-) -> ExecutionPlan {
-    let mut task_names = HashSet::new();
-    let mut steps = Vec::new();
-
-    if !sub_flow {
-        match config.config.legacy_migration_task {
-            Some(ref task) => add_predefined_step(config, task, &mut steps),
-            None => debug!("Legacy migration task not defined."),
-        };
-        match config.config.init_task {
-            Some(ref task) => add_predefined_step(config, task, &mut steps),
-            None => debug!("Init task not defined."),
-        };
+#[derive(Clone, Debug)]
+pub(crate) struct ExecutionPlanBuilder<'a> {
+    pub config: &'a Config,
+    pub task: &'a str,
+    pub crate_info: Option<&'a CrateInfo>,
+    pub disable_workspace: bool,
+    pub allow_private: bool,
+    pub sub_flow: bool,
+    pub skip_tasks_pattern: Option<&'a Regex>,
+    pub skip_init_end_tasks: bool,
+}
+
+impl<'a> ExecutionPlanBuilder<'a> {
+    pub fn new(config: &'a Config, task: &'a str) -> Self {
+        Self {
+            config,
+            task,
+            crate_info: None,
+            disable_workspace: false,
+            allow_private: false,
+            sub_flow: false,
+            skip_tasks_pattern: None,
+            skip_init_end_tasks: false,
+        }
     }
 
-    let skip = match skip_tasks_pattern {
-        Some(ref pattern) => pattern.is_match(task),
-        None => false,
-    };
+    pub fn build(&self) -> ExecutionPlan {
+        let Self {
+            config,
+            task,
+            crate_info,
+            disable_workspace,
+            allow_private,
+            sub_flow,
+            skip_tasks_pattern,
+            skip_init_end_tasks,
+        } = *self;
+        let mut task_names = HashSet::new();
+        let mut steps = Vec::new();
+        let default_crate_info = CrateInfo::new();
+        let crate_info = crate_info.unwrap_or(&default_crate_info);
+        let skip_init_end_tasks = skip_init_end_tasks || sub_flow;
+
+        if !skip_init_end_tasks {
+            match config.config.legacy_migration_task {
+                Some(ref task) => add_predefined_step(config, task, &mut steps),
+                None => debug!("Legacy migration task not defined."),
+            };
+            match config.config.init_task {
+                Some(ref task) => add_predefined_step(config, task, &mut steps),
+                None => debug!("Init task not defined."),
+            };
+        }
+
+        let skip = match skip_tasks_pattern {
+            Some(pattern) => pattern.is_match(task),
+            None => false,
+        };
 
-    if !skip {
-        let workspace_flow =
-            is_workspace_flow(&config, &task, disable_workspace, &crate_info, sub_flow);
+        if !skip {
+            let workspace_flow =
+                is_workspace_flow(&config, &task, disable_workspace, &crate_info, sub_flow);
 
-        if workspace_flow {
-            let workspace_task = create_workspace_task(crate_info, task);
+            if workspace_flow {
+                let workspace_task = create_workspace_task(crate_info, task);
 
-            steps.push(Step {
-                name: "workspace".to_string(),
-                config: workspace_task,
-            });
+                steps.push(Step {
+                    name: "workspace".to_string(),
+                    config: workspace_task,
+                });
+            } else {
+                create_for_step(
+                    &config,
+                    &TaskIdentifier::from_name(task),
+                    &mut steps,
+                    &mut task_names,
+                    true,
+                    allow_private,
+                    skip_tasks_pattern,
+                );
+            }
         } else {
-            create_for_step(
-                &config,
-                &TaskIdentifier::from_name(task),
-                &mut steps,
-                &mut task_names,
-                true,
-                allow_private,
-                &skip_tasks_pattern,
-            );
+            debug!("Skipping task: {} due to skip pattern.", &task);
         }
-    } else {
-        debug!("Skipping task: {} due to skip pattern.", &task);
-    }
 
-    if !sub_flow {
-        // always add end task even if already executed due to some dependency
-        match config.config.end_task {
-            Some(ref task) => add_predefined_step(config, task, &mut steps),
-            None => debug!("Ent task not defined."),
-        };
-    }
+        if !skip_init_end_tasks {
+            // always add end task even if already executed due to some dependency
+            match config.config.end_task {
+                Some(ref task) => add_predefined_step(config, task, &mut steps),
+                None => debug!("Ent task not defined."),
+            };
+        }
 
-    ExecutionPlan { steps }
+        ExecutionPlan { steps }
+    }
 }
diff --git a/src/lib/runner.rs b/src/lib/runner.rs
index c774314a..aad2a0e0 100755
--- a/src/lib/runner.rs
+++ b/src/lib/runner.rs
@@ -15,7 +15,7 @@ mod runner_test;
 use crate::command;
 use crate::condition;
 use crate::environment;
-use crate::execution_plan::create as create_execution_plan;
+use crate::execution_plan::ExecutionPlanBuilder;
 use crate::functions;
 use crate::installer;
 use crate::logger;
@@ -563,15 +563,16 @@ fn create_watch_task(task: &str, options: Option<TaskWatchOptions>, flow_info: &
 pub(crate) fn run_flow(flow_info: &FlowInfo, flow_state: Rc<RefCell<FlowState>>, sub_flow: bool) {
     let allow_private = sub_flow || flow_info.allow_private;
 
-    let execution_plan = create_execution_plan(
-        &flow_info.config,
-        &flow_info.task,
-        &flow_info.env_info.crate_info,
-        flow_info.disable_workspace,
+    let execution_plan = ExecutionPlanBuilder {
+        crate_info: Some(&flow_info.env_info.crate_info),
+        disable_workspace: flow_info.disable_workspace,
         allow_private,
         sub_flow,
-        &flow_info.skip_tasks_pattern,
-    );
+        skip_tasks_pattern: flow_info.skip_tasks_pattern.as_ref(),
+        skip_init_end_tasks: flow_info.skip_init_end_tasks,
+        ..ExecutionPlanBuilder::new(&flow_info.config, &flow_info.task)
+    }
+    .build();
     debug!("Created execution plan: {:#?}", &execution_plan);
 
     run_task_flow(&flow_info, flow_state, &execution_plan);


Test patch:
diff --git a/src/lib/cli_commands/print_steps_test.rs b/src/lib/cli_commands/print_steps_test.rs
index 7ec991f3..2d0ce0a4 100644
--- a/src/lib/cli_commands/print_steps_test.rs
+++ b/src/lib/cli_commands/print_steps_test.rs
@@ -35,7 +35,17 @@ fn print_default_format() {
     config.tasks.insert("end".to_string(), Task::new());
     config.tasks.insert("test".to_string(), Task::new());
 
-    print(&config, "test", "default", false, None, &CrateInfo::new());
+    print(
+        &mut std::io::stdout(),
+        &config,
+        "test",
+        "default",
+        false,
+        None,
+        &CrateInfo::new(),
+        false,
+    )
+    .expect("print should succeed");
 }
 
 #[test]
@@ -53,7 +63,17 @@ fn print_task_not_found() {
     config.tasks.insert("init".to_string(), Task::new());
     config.tasks.insert("end".to_string(), Task::new());
 
-    print(&config, "test", "default", false, None, &CrateInfo::new());
+    print(
+        &mut std::io::stdout(),
+        &config,
+        "test",
+        "default",
+        false,
+        None,
+        &CrateInfo::new(),
+        false,
+    )
+    .expect("print should succeed");
 }
 
 #[test]
@@ -71,13 +91,16 @@ fn print_task_not_found_but_skipped() {
     config.tasks.insert("end".to_string(), Task::new());
 
     print(
+        &mut std::io::stdout(),
         &config,
         "test",
         "default",
         false,
         Some("test".to_string()),
         &CrateInfo::new(),
-    );
+        false,
+    )
+    .expect("print should succeed");
 }
 
 #[test]
@@ -89,7 +112,7 @@ fn print_default_valid() {
     let steps = vec![step];
     let execution_plan = ExecutionPlan { steps };
 
-    print_default(&execution_plan);
+    print_default(&mut std::io::stdout(), &execution_plan).expect("print should succeed");
 }
 
 #[test]
@@ -101,5 +124,58 @@ fn print_short_description_valid() {
     let steps = vec![step];
     let execution_plan = ExecutionPlan { steps };
 
-    print_short_description(&execution_plan);
+    print_short_description(&mut std::io::stdout(), &execution_plan).expect("print should succeed");
+}
+
+#[test]
+fn print_skip_init_end_tasks() {
+    // Use a unique string, so that we are certain it shouldn't appear in the output.
+    let init_task_name = "init_5ec3_5b28_7b73_dcee";
+    let end_task_name = "end_3afa_4ede_b49a_1767";
+
+    let tasks = IndexMap::from([
+        (init_task_name.to_string(), Task::new()),
+        (end_task_name.to_string(), Task::new()),
+        ("entry".to_string(), Task::new()),
+    ]);
+
+    // Test with only init_task enabled.
+    let config = Config {
+        config: ConfigSection {
+            init_task: Some(init_task_name.to_string()),
+            end_task: Some(end_task_name.to_string()),
+            ..ConfigSection::new()
+        },
+        env_files: vec![],
+        env: IndexMap::new(),
+        env_scripts: vec![],
+        tasks,
+        plugins: None,
+    };
+
+    let mut output_bytes = Vec::<u8>::new();
+    print(
+        &mut output_bytes,
+        &config,
+        "entry",
+        "default",
+        false,
+        None,
+        &CrateInfo::new(),
+        true,
+    )
+    .expect("print should succeed");
+    let output = std::str::from_utf8(&output_bytes).expect("output must be valid UTF-8 strings");
+    assert!(
+        !output.contains(init_task_name),
+        "output {} shouldn't contain the init task name {}",
+        output,
+        init_task_name
+    );
+    assert!(
+        !output.contains(end_task_name),
+        "output {} shouldn't contain the end task name {}",
+        output,
+        end_task_name
+    );
 }
diff --git a/src/lib/execution_plan_test.rs b/src/lib/execution_plan_test.rs
index 4778fb99..7b2c2a62 100644
--- a/src/lib/execution_plan_test.rs
+++ b/src/lib/execution_plan_test.rs
@@ -789,15 +789,11 @@ fn create_single() {
 
     config.tasks.insert("test".to_string(), task);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 3);
     assert_eq!(execution_plan.steps[0].name, "init");
     assert_eq!(execution_plan.steps[1].name, "test");
@@ -826,15 +822,11 @@ fn create_single_disabled() {
 
     config.tasks.insert("test".to_string(), task);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 2);
     assert_eq!(execution_plan.steps[0].name, "init");
     assert_eq!(execution_plan.steps[1].name, "end");
@@ -863,15 +855,7 @@ fn create_single_private() {
 
     config.tasks.insert("test-private".to_string(), task);
 
-    create(
-        &config,
-        "test-private",
-        &CrateInfo::new(),
-        false,
-        false,
-        false,
-        &None,
-    );
+    ExecutionPlanBuilder::new(&config, "test-private").build();
 }
 
 #[test]
@@ -896,15 +880,11 @@ fn create_single_allow_private() {
 
     config.tasks.insert("test-private".to_string(), task);
 
-    let execution_plan = create(
-        &config,
-        "test-private",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test-private")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 3);
     assert_eq!(execution_plan.steps[0].name, "init");
     assert_eq!(execution_plan.steps[1].name, "test-private");
@@ -938,15 +918,11 @@ fn create_with_dependencies() {
         .tasks
         .insert("task_dependency".to_string(), task_dependency);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 4);
     assert_eq!(execution_plan.steps[0].name, "init");
     assert_eq!(execution_plan.steps[1].name, "task_dependency");
@@ -984,15 +960,11 @@ fn create_with_foreign_dependencies_directory() {
         .tasks
         .insert("task_dependency".to_string(), task_dependency);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
 
     assert_eq!(execution_plan.steps.len(), 4);
     assert_eq!(execution_plan.steps[0].name, "init");
@@ -1035,15 +1007,11 @@ fn create_with_foreign_dependencies_filename() {
         .tasks
         .insert("task_dependency".to_string(), task_dependency);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
 
     assert_eq!(execution_plan.steps.len(), 4);
     assert_eq!(execution_plan.steps[0].name, "init");
@@ -1086,15 +1054,11 @@ fn create_with_foreign_dependencies_file_and_directory() {
         .tasks
         .insert("task_dependency".to_string(), task_dependency);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
 
     assert_eq!(execution_plan.steps.len(), 4);
     assert_eq!(execution_plan.steps[0].name, "init");
@@ -1134,7 +1098,12 @@ fn create_with_dependencies_sub_flow() {
         .tasks
         .insert("task_dependency".to_string(), task_dependency);
 
-    let execution_plan = create(&config, "test", &CrateInfo::new(), false, true, true, &None);
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        sub_flow: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 2);
     assert_eq!(execution_plan.steps[0].name, "task_dependency");
     assert_eq!(execution_plan.steps[1].name, "test");
@@ -1168,15 +1137,11 @@ fn create_disabled_task_with_dependencies() {
         .tasks
         .insert("task_dependency".to_string(), task_dependency);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 2);
     assert_eq!(execution_plan.steps[0].name, "init");
     assert_eq!(execution_plan.steps[1].name, "end");
@@ -1210,15 +1175,11 @@ fn create_with_dependencies_disabled() {
         .tasks
         .insert("task_dependency".to_string(), task_dependency);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 3);
     assert_eq!(execution_plan.steps[0].name, "init");
     assert_eq!(execution_plan.steps[1].name, "test");
@@ -1327,15 +1288,11 @@ fn create_platform_disabled() {
 
     config.tasks.insert("test".to_string(), task);
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 0);
 }
 
@@ -1369,15 +1326,12 @@ fn create_with_dependencies_and_skip_filter() {
 
     let skip_filter = Regex::new("filtered.*").unwrap();
 
-    let execution_plan = create(
-        &config,
-        "test",
-        &CrateInfo::new(),
-        false,
-        true,
-        false,
-        &Some(skip_filter),
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        skip_tasks_pattern: Some(&skip_filter),
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     assert_eq!(execution_plan.steps.len(), 4);
     assert_eq!(execution_plan.steps[0].name, "init");
     assert_eq!(execution_plan.steps[1].name, "task_dependency");
@@ -1403,7 +1357,12 @@ fn create_workspace() {
 
     env::set_current_dir("./examples/workspace").unwrap();
     let crateinfo = environment::crateinfo::load();
-    let execution_plan = create(&config, "test", &crateinfo, false, true, false, &None);
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        crate_info: Some(&crateinfo),
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     env::set_current_dir("../../").unwrap();
     assert_eq!(execution_plan.steps.len(), 1);
     assert_eq!(execution_plan.steps[0].name, "workspace");
@@ -1427,7 +1386,13 @@ fn create_noworkspace() {
 
     env::set_current_dir("./examples/workspace").unwrap();
     let crateinfo = environment::crateinfo::load();
-    let execution_plan = create(&config, "test", &crateinfo, true, true, false, &None);
+    let execution_plan = ExecutionPlanBuilder {
+        disable_workspace: true,
+        allow_private: true,
+        crate_info: Some(&crateinfo),
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
     env::set_current_dir("../../").unwrap();
     assert_eq!(execution_plan.steps.len(), 1);
     assert_eq!(execution_plan.steps[0].name, "test");
@@ -1444,15 +1409,11 @@ fn create_task_extends_empty_env_bug_verification() {
     )
     .unwrap();
 
-    let execution_plan = create(
-        &config,
-        "task2",
-        &CrateInfo::new(),
-        true,
-        false,
-        false,
-        &None,
-    );
+    let execution_plan = ExecutionPlanBuilder {
+        disable_workspace: true,
+        ..ExecutionPlanBuilder::new(&config, "task2")
+    }
+    .build();
 
     assert_eq!(execution_plan.steps.len(), 3);
 
@@ -1623,3 +1584,34 @@ fn get_normalized_task_simple() {
     assert_eq!(task.command.unwrap(), "echo");
     assert_eq!(task.args.unwrap(), vec!["1".to_string()]);
 }
+
+#[test]
+fn respect_skip_init_end_tasks() {
+    let mut config_section = ConfigSection::new();
+    config_section.init_task = Some("init".to_string());
+    config_section.end_task = Some("end".to_string());
+    let mut config = Config {
+        config: config_section,
+        env_files: vec![],
+        env: IndexMap::new(),
+        env_scripts: vec![],
+        tasks: IndexMap::new(),
+        plugins: None,
+    };
+
+    config.tasks.insert("init".to_string(), Task::new());
+    config.tasks.insert("end".to_string(), Task::new());
+
+    let task = Task::new();
+
+    config.tasks.insert("test".to_string(), task);
+
+    let execution_plan = ExecutionPlanBuilder {
+        allow_private: true,
+        skip_init_end_tasks: true,
+        ..ExecutionPlanBuilder::new(&config, "test")
+    }
+    .build();
+    assert_eq!(execution_plan.steps.len(), 1);
+    assert_eq!(execution_plan.steps[0].name, "test");
+}
diff --git a/src/lib/runner_test.rs b/src/lib/runner_test.rs
index 4b645a82..13ffb2c5 100755
--- a/src/lib/runner_test.rs
+++ b/src/lib/runner_test.rs
@@ -2501,3 +2501,71 @@ fn create_fork_step_valid() {
     assert_eq!(args[8], makefile);
     assert_eq!(args[9], "test".to_string());
 }
+
+#[test]
+#[ignore]
+fn run_flow_skip_init_end_tasks() {
+    // Init logger to panic at error.
+    crate::test::on_test_startup();
+
+    let fail_task = Task {
+        // Use duckscript, so that the behavior of the script is the same on all platforms.
+        script_runner: Some("@duckscript".to_string()),
+        script: Some(ScriptValue::Text(vec!["exit 1".to_string()])),
+        ..Task::new()
+    };
+
+    let entry_task = Task {
+        script_runner: Some("@duckscript".to_string()),
+        script: Some(ScriptValue::Text(vec!["echo test".to_string()])),
+        ..Task::new()
+    };
+
+    let tasks = IndexMap::from([
+        ("fail".to_string(), fail_task),
+        ("entry".to_string(), entry_task),
+    ]);
+
+    // Test with only init_task existing.
+    let config = Config {
+        config: ConfigSection {
+            init_task: Some("fail".to_string()),
+            ..ConfigSection::new()
+        },
+        env_files: vec![],
+        env: IndexMap::new(),
+        env_scripts: vec![],
+        tasks,
+        plugins: None,
+    };
+    let flow_info = FlowInfo {
+        config,
+        task: "entry".to_string(),
+        env_info: EnvInfo {
+            rust_info: RustInfo::new(),
+            crate_info: CrateInfo::new(),
+            git_info: GitInfo::new(),
+            ci_info: ci_info::get(),
+        },
+        disable_workspace: false,
+        disable_on_error: false,
+        allow_private: false,
+        skip_init_end_tasks: true,
+        skip_tasks_pattern: None,
+        cli_arguments: None,
+    };
+
+    run_flow(&flow_info, Rc::new(RefCell::new(FlowState::new())), false);
+
+    // Test with only end_task existing.
+    let flow_info = {
+        let mut flow_info = flow_info;
+        flow_info.config.config = ConfigSection {
+            end_task: Some("fail".to_string()),
+            ..ConfigSection::new()
+        };
+        flow_info
+    };
+
+    run_flow(&flow_info, Rc::new(RefCell::new(FlowState::new())), false);
+}