forc_debug/server/handlers/
handle_launch.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use crate::server::{AdapterError, DapServer};
use crate::types::Instruction;
use forc_pkg::manifest::GenericManifestFile;
use forc_pkg::{self, BuildProfile, Built, BuiltPackage, PackageManifestFile};
use forc_test::execute::TestExecutor;
use forc_test::setup::TestSetup;
use forc_test::BuiltTests;
use std::{collections::HashMap, sync::Arc};
use sway_types::LineCol;

impl DapServer {
    /// Handles a `launch` request. Returns true if the server should continue running.
    pub fn handle_launch(&mut self) -> Result<bool, AdapterError> {
        // Build tests for the given path.
        let (pkg_to_debug, test_setup) = self.build_tests()?;
        let entries = pkg_to_debug.bytecode.entries.iter().filter_map(|entry| {
            if let Some(test_entry) = entry.kind.test() {
                return Some((entry, test_entry));
            }
            None
        });

        // Construct a TestExecutor for each test and store it
        let executors: Vec<TestExecutor> = entries
            .filter_map(|(entry, test_entry)| {
                let offset = u32::try_from(entry.finalized.imm)
                    .expect("test instruction offset out of range");
                let name = entry.finalized.fn_name.clone();
                if test_entry.file_path.as_path() != self.state.program_path.as_path() {
                    return None;
                }

                TestExecutor::build(
                    &pkg_to_debug.bytecode.bytes,
                    offset,
                    test_setup.clone(),
                    test_entry,
                    name.clone(),
                )
                .ok()
            })
            .collect();
        self.state.init_executors(executors);

        // Start debugging
        self.start_debugging_tests(false)
    }

    /// Builds the tests at the given [PathBuf] and stores the source maps.
    pub(crate) fn build_tests(&mut self) -> Result<(BuiltPackage, TestSetup), AdapterError> {
        if let Some(pkg) = &self.state.built_package {
            if let Some(setup) = &self.state.test_setup {
                return Ok((pkg.clone(), setup.clone()));
            }
        }

        // 1. Build the packages
        let manifest_file = forc_pkg::manifest::ManifestFile::from_dir(&self.state.program_path)
            .map_err(|err| AdapterError::BuildFailed {
                reason: format!("read manifest file: {:?}", err),
            })?;
        let pkg_manifest: PackageManifestFile =
            manifest_file
                .clone()
                .try_into()
                .map_err(|err: anyhow::Error| AdapterError::BuildFailed {
                    reason: format!("package manifest: {:?}", err),
                })?;
        let member_manifests =
            manifest_file
                .member_manifests()
                .map_err(|err| AdapterError::BuildFailed {
                    reason: format!("member manifests: {:?}", err),
                })?;
        let lock_path = manifest_file
            .lock_path()
            .map_err(|err| AdapterError::BuildFailed {
                reason: format!("lock path: {:?}", err),
            })?;
        let build_plan = forc_pkg::BuildPlan::from_lock_and_manifests(
            &lock_path,
            &member_manifests,
            false,
            false,
            &Default::default(),
        )
        .map_err(|err| AdapterError::BuildFailed {
            reason: format!("build plan: {:?}", err),
        })?;

        let project_name = pkg_manifest.project_name();

        let outputs = std::iter::once(build_plan.find_member_index(project_name).ok_or(
            AdapterError::BuildFailed {
                reason: format!("find built project: {}", project_name),
            },
        )?)
        .collect();

        let built_packages = forc_pkg::build(
            &build_plan,
            Default::default(),
            &BuildProfile {
                optimization_level: sway_core::OptLevel::Opt0,
                include_tests: true,
                ..Default::default()
            },
            &outputs,
            &[],
            &[],
        )
        .map_err(|err| AdapterError::BuildFailed {
            reason: format!("build packages: {:?}", err),
        })?;

        // 2. Store the source maps
        let mut pkg_to_debug: Option<&BuiltPackage> = None;
        built_packages.iter().for_each(|(_, built_pkg)| {
            if built_pkg.descriptor.manifest_file == pkg_manifest {
                pkg_to_debug = Some(built_pkg);
            }
            let source_map = &built_pkg.source_map;

            let paths = &source_map.paths;
            source_map.map.iter().for_each(|(instruction, sm_span)| {
                if let Some(path_buf) = paths.get(sm_span.path.0) {
                    let LineCol { line, .. } = sm_span.range.start;
                    let (line, instruction) = (line as i64, *instruction as Instruction);

                    self.state
                        .source_map
                        .entry(path_buf.clone())
                        .and_modify(|new_map| {
                            new_map
                                .entry(line)
                                .and_modify(|val| {
                                    // Store the instructions in ascending order
                                    match val.binary_search(&instruction) {
                                        Ok(_) => {} // Ignore duplicates
                                        Err(pos) => val.insert(pos, instruction),
                                    }
                                })
                                .or_insert(vec![instruction]);
                        })
                        .or_insert(HashMap::from([(line, vec![instruction])]));
                } else {
                    self.error(format!(
                        "Path missing from source map: {:?}",
                        sm_span.path.0
                    ));
                }
            });
        });

        // 3. Build the tests
        let built_package = pkg_to_debug.ok_or(AdapterError::BuildFailed {
            reason: format!("find package: {}", project_name),
        })?;

        let built = Built::Package(Arc::from(built_package.clone()));

        let built_tests = BuiltTests::from_built(built, &build_plan).map_err(|err| {
            AdapterError::BuildFailed {
                reason: format!("build tests: {:?}", err),
            }
        })?;

        let pkg_tests = match built_tests {
            BuiltTests::Package(pkg_tests) => pkg_tests,
            BuiltTests::Workspace(_) => {
                return Err(AdapterError::BuildFailed {
                    reason: "package tests: workspace tests not supported".into(),
                })
            }
        };
        let test_setup = pkg_tests.setup().map_err(|err| AdapterError::BuildFailed {
            reason: format!("test setup: {:?}", err),
        })?;
        self.state.built_package = Some(built_package.clone());
        self.state.test_setup = Some(test_setup.clone());
        Ok((built_package.clone(), test_setup))
    }
}