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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
#![allow(clippy::module_name_repetitions)]

use crate::objects::codemodel_v2::{Directory, Target};
use crate::objects::{MajorMinor, Object, ObjectKind};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use crate::reply;

/// The codemodel object kind describes the build system structure as modeled by `CMake`.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct CodeModel {
    /// Kind of the codemodel object.
    pub kind: ObjectKind,

    /// Version of the codemodel object.
    pub version: MajorMinor,

    /// Paths of the codemodel object.
    pub paths: CodemodelPaths,

    /// Available build configurations.
    /// On single-configuration generators there is one entry for the value of the CMAKE_BUILD_TYPE variable.
    /// For multi-configuration generators there is an entry for each configuration listed in the CMAKE_CONFIGURATION_TYPES variable.
    pub configurations: Vec<Configuration>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct CodemodelPaths {
    /// Absolute path to the top-level source directory, represented with forward slashes.
    pub build: PathBuf,

    /// Absolute path to the top-level build directory, represented with forward slashes.
    pub source: PathBuf,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Configuration {
    /// A string specifying the name of the configuration, e.g. Debug.
    pub name: String,

    /// Top-level project and subprojects defined in the build system.
    /// Each (sub-)project corresponds to a source directory whose CMakeLists.txt file calls the project() command with a project name different from its parent directory.
    /// The first entry corresponds to the top-level project.
    pub projects: Vec<Project>,

    /// Build system directory info whose source directory contains a CMakeLists.txt file.
    /// The first entry corresponds to the top-level directory
    #[serde(rename = "directories")]
    pub directory_refs: Vec<DirectoryReference>,

    /// Build system targets.
    /// Such targets are created by calls to add_executable(), add_library(), and add_custom_target(),
    /// excluding imported targets and interface libraries (which do not generate any build rules).
    #[serde(rename = "targets")]
    pub target_refs: Vec<TargetReference>,

    /// The following members are not part of the JSON file.
    /// They are used to store the actual objects that the references point to.

    /// Directory objects.
    /// The position in the vector corresponds to the index in the directory_refs vector.
    #[serde(skip)]
    pub directories: Vec<Directory>,

    /// Target objects.
    /// The position in the vector corresponds to the index in the target_refs vector.
    #[serde(skip)]
    pub targets: Vec<Target>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct DirectoryReference {
    /// Path to the source directory, represented with forward slashes.
    /// If the directory is inside the top-level source directory then the path is specified
    /// relative to that directory (with . for the top-level source directory itself).
    /// Otherwise, the path is absolute.
    pub source: PathBuf,

    /// Path to the build directory, represented with forward slashes.
    /// If the directory is inside the top-level build directory then the path is specified
    /// relative to that directory (with . for the top-level build directory itself).
    /// Otherwise, the path is absolute.
    pub build: PathBuf,

    /// Optional member that is present when the directory is not top-level.
    /// The value is an unsigned integer 0-based index of another entry in the main directories array
    /// that corresponds to the parent directory that added this directory as a subdirectory.
    pub parent_index: Option<usize>,

    /// Optional member that is present when the directory has subdirectories.
    /// Each entry corresponding to child directory created by the add_subdirectory() or subdirs() command.
    /// Each entry is an unsigned integer 0-based index of another entry in the main directories array.
    #[serde(default)]
    pub child_indexes: Vec<usize>,

    /// An unsigned integer 0-based index into the main projects array indicating the build system project to which the directory belongs.
    pub project_index: usize,

    /// Optional member that is present when the directory itself has targets, excluding those belonging to subdirectories.
    /// Each entry corresponding to the targets.
    /// Each entry is an unsigned integer 0-based index into the main targets array.
    #[serde(default)]
    pub target_indexes: Vec<usize>,

    /// Optional member present when a minimum required version of CMake is known for the directory.
    /// This is the `<min>` version given to the most local call to the cmake_minimum_required(VERSION) command in the directory itself or
    /// one of its ancestors.
    #[serde(rename = "minimumCMakeVersion")]
    pub minimum_cmake_version: Option<MinimumCmakeVersion>,

    /// True when the directory or one of its subdirectories contains any install() rules, i.e. whether a make install or equivalent rule is available.
    #[serde(default)]
    pub has_install_rule: bool,

    /// Path relative to the codemodel file to another JSON file containing a "codemodel" version 2 "directory" object.
    pub json_file: PathBuf,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct MinimumCmakeVersion {
    /// A string specifying the minimum required version in the format
    /// \<major\>.\<minor\>.\[\<patch\>\[.\<tweak\>]]\[\<suffix\>]
    /// Each component is an unsigned integer and the suffix may be an arbitrary string.
    #[serde(rename = "string")]
    pub version: String,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct Project {
    /// A string specifying the name given to the project() command.
    pub name: String,

    /// Optional member that is present when the project is not top-level.
    /// The value is an unsigned integer 0-based index of another entry in the main projects array that corresponds to the parent project
    /// that added this project as a subproject.
    pub parent_index: Option<usize>,

    /// Optional member that is present when the project has subprojects.
    /// Entries corresponding to the subprojects.
    /// Each entry is an unsigned integer 0-based index of another entry in the main projects array.
    #[serde(default)]
    pub child_indexes: Vec<usize>,

    /// Entries corresponding to build system directories that are part of the project.
    /// The first entry corresponds to the top-level directory of the project.
    /// Each entry is an unsigned integer 0-based index into the main directories array.
    pub directory_indexes: Vec<usize>,

    /// Optional member that is present when the project itself has targets, excluding those belonging to subprojects.
    /// Entries corresponding to the targets.
    /// Each entry is an unsigned integer 0-based index into the main targets array.
    #[serde(default)]
    pub target_indexes: Vec<usize>,
}

#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
#[non_exhaustive]
pub struct TargetReference {
    /// A string specifying the target name.
    pub name: String,

    /// A string uniquely identifying the target.
    /// This matches the id field in the file referenced by jsonFile.
    pub id: String,

    /// An unsigned integer 0-based index into the main directories array indicating
    /// the build system directory in which the target is defined.
    pub directory_index: usize,

    /// An unsigned integer 0-based index into the main projects array indicating the
    /// build system project in which the target is defined.
    pub project_index: usize,

    /// Path relative to the codemodel file to another JSON file containing a "codemodel" version 2 "target" object.
    pub json_file: PathBuf,
}

impl Object for CodeModel {
    fn kind() -> ObjectKind {
        ObjectKind::CodeModel
    }

    fn major() -> u32 {
        2
    }

    fn resolve_references(&mut self, reader: &reply::Reader) -> Result<(), reply::ReaderError> {
        let reply_dir = reply::dir(reader.build_dir());

        // resolve targets and directories references
        for config in &mut self.configurations {
            for target_ref in &config.target_refs {
                config
                    .targets
                    .push(reply::Reader::parse_reply(reply_dir.join(&target_ref.json_file))?);
            }

            for directory_ref in &config.directory_refs {
                config.directories.push(reply::Reader::parse_reply(
                    reply_dir.join(&directory_ref.json_file),
                )?);
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::objects;
    use crate::objects::codemodel_v2::*;
    use crate::objects::MajorMinor;
    use serde_json::json;
    use std::path::PathBuf;

    #[test]
    fn test_model() {
        let json = json!({
          "kind": "codemodel",
          "version": { "major": 2, "minor": 6 },
          "paths": {
            "source": "/path/to/top-level-source-dir",
            "build": "/path/to/top-level-build-dir"
          },
          "configurations": [
            {
              "name": "Debug",
              "directories": [
                {
                  "source": ".",
                  "build": ".",
                  "childIndexes": [ 1 ],
                  "projectIndex": 0,
                  "targetIndexes": [ 0 ],
                  "hasInstallRule": true,
                  "minimumCMakeVersion": {
                    "string": "3.14"
                  },
                  "jsonFile": "<file>"
                },
                {
                  "source": "sub",
                  "build": "sub",
                  "parentIndex": 0,
                  "projectIndex": 0,
                  "targetIndexes": [ 1 ],
                  "minimumCMakeVersion": {
                    "string": "3.14"
                  },
                  "jsonFile": "<file>"
                }
              ],
              "projects": [
                {
                  "name": "MyProject",
                  "directoryIndexes": [ 0, 1 ],
                  "targetIndexes": [ 0, 1 ]
                }
              ],
              "targets": [
                {
                  "name": "MyExecutable",
                  "directoryIndex": 0,
                  "projectIndex": 0,
                  "jsonFile": "<file>",
                  "id": "0"
                },
                {
                  "name": "MyLibrary",
                  "directoryIndex": 1,
                  "projectIndex": 0,
                  "jsonFile": "<file>",
                  "id": "1"
                }
              ]
            }
          ]
        });

        let model = serde_json::from_value::<CodeModel>(json).unwrap();
        assert_eq!(model.kind, objects::ObjectKind::CodeModel);
        assert_eq!(model.version, MajorMinor { major: 2, minor: 6 });
        assert_eq!(
            model.paths,
            CodemodelPaths {
                source: "/path/to/top-level-source-dir".into(),
                build: "/path/to/top-level-build-dir".into()
            }
        );
        assert_eq!(model.configurations.len(), 1);
        assert_eq!(model.configurations[0].name, "Debug");
        assert_eq!(model.configurations[0].directory_refs.len(), 2);
        assert_eq!(
            model.configurations[0].directory_refs[0].source,
            PathBuf::from(".")
        );
        assert_eq!(model.configurations[0].projects.len(), 1);
        assert_eq!(model.configurations[0].projects[0].name, "MyProject");
        assert_eq!(model.configurations[0].target_refs.len(), 2);
        assert_eq!(model.configurations[0].target_refs[0].name, "MyExecutable");
    }
}