Skip to content

Commit 5c09bc3

Browse files
author
Steve Ramage
committed
feat: Add support for nspawn files (Resolves #290)
1 parent a8c95d8 commit 5c09bc3

30 files changed

+1272
-675
lines changed

README.md

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
Unit File Support for systemd
2-
-----------------------------
1+
systemd & Unit File Support
2+
---------------------------
33

44
## Introduction
55

6-
This plugin adds support for [systemd unit files](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#) to IntelliJ.
6+
This plugin adds support for [systemd & unit files](https://www.freedesktop.org/software/systemd/man/systemd.unit.html#) to IntelliJ.
77

88
## Features
99
* Syntax highlighting for unit files.
@@ -28,24 +28,27 @@ This plugin adds support for [systemd unit files](https://www.freedesktop.org/so
2828
2929
## Usage
3030
To create a file simply right-click on a folder and <kbd>New</kbd> > <kbd>File</kbd>, and enter a file name ending any of:
31-
* `.automount`
32-
* `.device`
33-
* `.mount`
34-
* `.path`
35-
* `.service`
36-
* `.slice`
37-
* `.socket`
38-
* `.swap`
39-
* `.target`
40-
* `.timer`
31+
* [Unit Files](https://www.freedesktop.org/software/systemd/man/latest/systemd.unit.html)
32+
* `.automount`
33+
* `.device`
34+
* `.mount`
35+
* `.path`
36+
* `.service`
37+
* `.slice`
38+
* `.socket`
39+
* `.swap`
40+
* `.target`
41+
* `.timer`
42+
* [Nspawn Container Settings](https://www.freedesktop.org/software/systemd/man/latest/systemd.nspawn.html#)
43+
* `.nspawn`
4144

4245
The file should then be associated with this plugin and the above features should work.
43-
46+
4447
__NOTE__: `.scope` units are not configured via unit configuration files and so we don't support them.
4548

4649
## Installation
4750

48-
This plugin is avaliable to install at the [JetBrains Plugin Repository](https://plugins.jetbrains.com/plugin/11070-unit-file-support-systemd-).
51+
This plugin is available to install at the [JetBrains Plugin Repository](https://plugins.jetbrains.com/plugin/11070-unit-file-support-systemd-).
4952

5053
Contributors
5154
-------------

build.gradle.kts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,21 @@ tasks.register<GenerateParserTask>("generateParserTask") {
202202

203203

204204
tasks.register<Copy>("generateOptionValidator") {
205-
from("./systemd-build/build/load-fragment-gperf.gperf")
205+
listOf(
206+
"journald-gperf.gperf",
207+
"link-config-gperf.gperf",
208+
"load-fragment-gperf.gperf",
209+
"logind-gperf.gperf",
210+
"netdev-gperf.gperf",
211+
"networkd-gperf.gperf",
212+
"networkd-network-gperf.gperf",
213+
"nspawn-gperf.gperf",
214+
"resolved-dnssd-gperf.gperf",
215+
"resolved-gperf.gperf",
216+
"timesyncd-gperf.gperf").forEach {
217+
fileName -> from("./systemd-build/build/${fileName}")
218+
}
219+
206220
into("${sourceSets["main"].output.resourcesDir?.getAbsolutePath()}/net/sjrx/intellij/plugins/systemdunitfiles/semanticdata/")
207221
}
208222

buildSrc/src/main/groovy/GenerateDataFromManPages.groovy

Lines changed: 111 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -55,75 +55,86 @@ class GenerateDataFromManPages extends DefaultTask {
5555
* Map that stores for each file name, the name of an option attribute
5656
*/
5757
@Internal
58-
def fileAndSectionTitleToSectionName = [
59-
'systemd.unit.xml' :
60-
['sections':
61-
['[Unit] Section Options' : ['Unit'],
62-
'[Install] Section Options': ['Install'],
63-
'Conditions and Asserts' : ['Unit']
64-
]
65-
],
66-
'systemd.service.xml' :
67-
['sections':
68-
['Options': ['Service']]
69-
],
70-
'systemd.timer.xml' :
71-
['sections':
72-
['Options': ['Timer']]
73-
],
74-
'systemd.automount.xml' :
75-
['sections':
76-
['Options': ['Automount']]
77-
],
78-
'systemd.mount.xml' :
79-
['sections':
80-
['Options': ['Mount']]
81-
],
82-
'systemd.path.xml' :
83-
['sections':
84-
['Options': ['Path']]
85-
],
86-
'systemd.socket.xml' :
87-
['sections':
88-
['Options': ['Socket']]
89-
],
90-
'systemd.swap.xml' :
91-
['sections':
92-
['Options': ['Swap']]
93-
],
94-
'systemd.resource-control.xml':
95-
['sections':
96-
[
97-
'Options' : ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
98-
'Deprecated Options': ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
99-
]
100-
],
101-
'systemd.kill.xml' :
102-
['sections':
103-
['Options': ['Service', "Socket", "Mount", "Swap"]]
104-
],
105-
'systemd.exec.xml' :
106-
['sections':
107-
[
108-
'Paths' : ['Service', 'Socket', 'Mount', 'Swap'],
109-
'Credentials' : ['Service', 'Socket', 'Mount', 'Swap'],
110-
'User/Group Identity' : ['Service', 'Socket', 'Mount', 'Swap'],
111-
'Capabilities' : ['Service', 'Socket', 'Mount', 'Swap'],
112-
'Security' : ['Service', 'Socket', 'Mount', 'Swap'],
113-
'Mandatory Access Control' : ['Service', 'Socket', 'Mount', 'Swap'],
114-
'Process Properties' : ['Service', 'Socket', 'Mount', 'Swap'],
115-
'Scheduling' : ['Service', 'Socket', 'Mount', 'Swap'],
116-
'Sandboxing' : ['Service', 'Socket', 'Mount', 'Swap'],
117-
'System Call Filtering' : ['Service', 'Socket', 'Mount', 'Swap'],
118-
'Environment' : ['Service', 'Socket', 'Mount', 'Swap'],
119-
'Logging and Standard Input/Output': ['Service', 'Socket', 'Mount', 'Swap'],
120-
'System V Compatibility' : ['Service', 'Socket', 'Mount', 'Swap'],
121-
]
122-
]
58+
def fileTypeToFileAndSectionTitleToSectionName = [
59+
'unit' : [
60+
'systemd.unit.xml' :
61+
['sections':
62+
['[Unit] Section Options' : ['Unit'],
63+
'[Install] Section Options': ['Install'],
64+
'Conditions and Asserts' : ['Unit']
65+
]
66+
],
67+
'systemd.service.xml' :
68+
['sections':
69+
['Options': ['Service']]
70+
],
71+
'systemd.timer.xml' :
72+
['sections':
73+
['Options': ['Timer']]
74+
],
75+
'systemd.automount.xml' :
76+
['sections':
77+
['Options': ['Automount']]
78+
],
79+
'systemd.mount.xml' :
80+
['sections':
81+
['Options': ['Mount']]
82+
],
83+
'systemd.path.xml' :
84+
['sections':
85+
['Options': ['Path']]
86+
],
87+
'systemd.socket.xml' :
88+
['sections':
89+
['Options': ['Socket']]
90+
],
91+
'systemd.swap.xml' :
92+
['sections':
93+
['Options': ['Swap']]
94+
],
95+
'systemd.resource-control.xml':
96+
['sections':
97+
[
98+
'Options' : ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
99+
'Deprecated Options': ['Slice', 'Service', 'Socket', 'Mount', 'Swap'],
100+
]
101+
],
102+
'systemd.kill.xml' :
103+
['sections':
104+
['Options': ['Service', "Socket", "Mount", "Swap"]]
105+
],
106+
'systemd.exec.xml' :
107+
['sections':
108+
[
109+
'Paths' : ['Service', 'Socket', 'Mount', 'Swap'],
110+
'Credentials' : ['Service', 'Socket', 'Mount', 'Swap'],
111+
'User/Group Identity' : ['Service', 'Socket', 'Mount', 'Swap'],
112+
'Capabilities' : ['Service', 'Socket', 'Mount', 'Swap'],
113+
'Security' : ['Service', 'Socket', 'Mount', 'Swap'],
114+
'Mandatory Access Control' : ['Service', 'Socket', 'Mount', 'Swap'],
115+
'Process Properties' : ['Service', 'Socket', 'Mount', 'Swap'],
116+
'Scheduling' : ['Service', 'Socket', 'Mount', 'Swap'],
117+
'Sandboxing' : ['Service', 'Socket', 'Mount', 'Swap'],
118+
'System Call Filtering' : ['Service', 'Socket', 'Mount', 'Swap'],
119+
'Environment' : ['Service', 'Socket', 'Mount', 'Swap'],
120+
'Logging and Standard Input/Output': ['Service', 'Socket', 'Mount', 'Swap'],
121+
'System V Compatibility' : ['Service', 'Socket', 'Mount', 'Swap'],
122+
]
123+
]],
124+
'nspawn': [
125+
'systemd.nspawn.xml':
126+
['sections':
127+
[
128+
'[Exec] Section Options' : ['Exec'],
129+
'[Files] Section Options' : ['Files'],
130+
'[Network] Section Options': ['Network'],
131+
]
132+
]]
133+
123134
]
124135

125136
@Internal
126-
Map<String /* Section */, Map<String /*Keyword*/, Map<String /*Attribute*/, String /*Value*/>>> sectionToKeyWordMapFromDoc = [:]
137+
Map<String /* File Type */, Map<String /* Section */, Map<String /*Keyword*/, Map<String /*Attribute*/, String /*Value*/>>>> fileTypeToSectionToKeyWordMapFromDoc = [:]
127138

128139
@Internal
129140
final XPath xpath
@@ -149,14 +160,22 @@ class GenerateDataFromManPages extends DefaultTask {
149160
logger.debug("Regenerating valid keys")
150161

151162

152-
fileAndSectionTitleToSectionName.keySet().each { file ->
153-
logger.debug("Starting $file")
154-
processFile(file)
163+
fileTypeToFileAndSectionTitleToSectionName.entrySet().each {
164+
fileToSectionToKeyWordMapFromDoc -> fileToSectionToKeyWordMapFromDoc.value.keySet().each {
165+
file ->
166+
logger.debug("Starting $file")
167+
var fileType = fileToSectionToKeyWordMapFromDoc.key
168+
processFile(fileType, file)
169+
}
170+
171+
155172
}
156173

174+
175+
157176
logger.debug("Complete")
158177

159-
def json = JsonOutput.toJson(this.sectionToKeyWordMapFromDoc)
178+
def json = JsonOutput.toJson(this.fileTypeToSectionToKeyWordMapFromDoc)
160179
json = JsonOutput.prettyPrint(json)
161180

162181
File outputData = new File(this.generatedJsonFileLocation.getAbsolutePath() + "/sectionToKeywordMapFromDoc.json")
@@ -173,20 +192,20 @@ class GenerateDataFromManPages extends DefaultTask {
173192
*
174193
* @param filename
175194
*/
176-
void processFile(String filename) {
195+
void processFile(String fileType, String filename) {
177196
File file = new File(this.systemdSourceCodeRoot.getAbsolutePath() + "/man/$filename")
178197

179-
generateKeywordAndValueJsonMapForFile(file)
198+
generateKeywordAndValueJsonMapForFile(fileType, file)
180199

181-
generateDocumentationHtmlFromManPages(file)
200+
generateDocumentationHtmlFromManPages(fileType, file)
182201
}
183202

184203
/**
185204
* Opens the file that will be scanned and extracts a list of variables from it storing it in JSON
186205
*
187206
* @param File file
188207
*/
189-
private void generateKeywordAndValueJsonMapForFile(File file) {
208+
private void generateKeywordAndValueJsonMapForFile(String fileType, File file) {
190209

191210
String filename = file.getName()
192211

@@ -206,12 +225,21 @@ class GenerateDataFromManPages extends DefaultTask {
206225
"/refentry/refsect1/variablelist[not(contains(@class,'environment-variables'))]/varlistentry",
207226
records, XPathConstants.NODESET);
208227
}
228+
else if (file.getAbsolutePath().endsWith("systemd.nspawn.xml")) {
229+
result = (NodeList)xpath.evaluate(
230+
"//variablelist[(contains(@class,'nspawn-directives'))]/varlistentry",
231+
records, XPathConstants.NODESET);
232+
}
209233
else {
210234
result = (NodeList)xpath.evaluate(
211235
"//variablelist[(contains(@class,'unit-directives'))]/varlistentry",
212236
records, XPathConstants.NODESET);
213237
}
214238

239+
if (result.getLength() == 0) {
240+
throw new IllegalStateException("Could not find variables under $filename")
241+
}
242+
215243

216244
for (int i = 0; i < result.getLength(); i++) {
217245
Node varListEntry = result.item(i)
@@ -227,19 +255,20 @@ class GenerateDataFromManPages extends DefaultTask {
227255
try {
228256

229257
String titleOfSection = xpath.evaluate("ancestor::refsect1/title[text()]", varListEntry)
230-
List<String> sections = fileAndSectionTitleToSectionName[filename]['sections'][titleOfSection]
258+
List<String> sections = fileTypeToFileAndSectionTitleToSectionName[fileType][filename]['sections'][titleOfSection]
231259

232260
String originalSection = xpath.evaluate("term/varname[text()]", varListEntry, XPathConstants.STRING)
233261

234262
String originalKeyName = getOptionNameAndValue(originalSection, filename)[0]
235263

236264
for (String section : sections) {
237265
logger.debug("Found options $section in $option in ${file.getAbsolutePath()}")
238-
sectionToKeyWordMapFromDoc.putIfAbsent(section, new TreeMap<>())
266+
fileTypeToSectionToKeyWordMapFromDoc.putIfAbsent(fileType, new TreeMap<>())
267+
fileTypeToSectionToKeyWordMapFromDoc.get(fileType).putIfAbsent(section, new TreeMap<>())
239268
def val = ["declaredInFile": filename]
240269
if (!keyValue.isEmpty()) val["values"] = keyValue
241270
if (keyName != originalKeyName) val["declaredUnderKeyword"] = originalKeyName
242-
sectionToKeyWordMapFromDoc[section][keyName] = val
271+
fileTypeToSectionToKeyWordMapFromDoc[fileType][section][keyName] = val
243272
}
244273
}
245274
catch (IllegalStateException e) {
@@ -272,14 +301,14 @@ class GenerateDataFromManPages extends DefaultTask {
272301
* @param File sourceFile - the source file to extract
273302
* @return
274303
*/
275-
private generateDocumentationHtmlFromManPages(File sourceFile) {
304+
private generateDocumentationHtmlFromManPages(String fileType, File sourceFile) {
276305
DocumentBuilder builder = dbf.newDocumentBuilder()
277306
Document document = builder.parse(sourceFile)
278307
Transformer transformer = getXsltTransformer()
279308

280309
String xsltOutput = transformDocument(document, transformer)
281310

282-
segmentParametersIntoFiles(sourceFile.getName(), xsltOutput)
311+
segmentParametersIntoFiles(fileType, sourceFile.getName(), xsltOutput)
283312
}
284313

285314
/**
@@ -339,7 +368,7 @@ class GenerateDataFromManPages extends DefaultTask {
339368
* @param sourceFileName - the name of the source file we pulled the data from
340369
* @param parameterInfoXMLAsString - A transformed XML document representing the documentation for systemd
341370
*/
342-
private void segmentParametersIntoFiles(String sourceFileName, String parameterInfoXMLAsString) {
371+
private void segmentParametersIntoFiles(String fileType, String sourceFileName, String parameterInfoXMLAsString) {
343372
def builder = dbf.newDocumentBuilder()
344373

345374
ByteArrayInputStream bis = new ByteArrayInputStream(parameterInfoXMLAsString.getBytes("UTF-8"))
@@ -368,11 +397,11 @@ class GenerateDataFromManPages extends DefaultTask {
368397

369398
String name = match.group(1)
370399

371-
List<String> foo = fileAndSectionTitleToSectionName[sourceFileName]['sections'][sectionTitle]
400+
List<String> foo = fileTypeToFileAndSectionTitleToSectionName[fileType][sourceFileName]['sections'][sectionTitle]
372401

373402
for (String sectionName : foo) {
374403
File outputFile = new File(
375-
this.generatedJsonFileLocation.getAbsolutePath() + "/documents/completion/" + sectionName + "/" + name + ".html")
404+
this.generatedJsonFileLocation.getAbsolutePath() + "/documents/completion/" + fileType + "/" + sectionName + "/" + name + ".html")
376405
outputFile.getParentFile().mkdirs()
377406

378407
Writer write = new BufferedWriter(new FileWriter(outputFile))

ci/release.Jenkinsfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,9 @@ pipeline {
310310

311311
sh("""
312312
mkdir -p ./systemd-build/build
313-
cp /opt/systemd-source/systemd/*.gperf ./systemd-build/build
313+
cat /opt/systemd-source/systemd/last_commit_date
314+
cat /opt/systemd-source/systemd/last_commit_hash
315+
cp /mount/* ./systemd-build/build
314316
cp /opt/systemd-source/systemd/last_commit_date /opt/systemd-source/systemd/last_commit_hash ./systemd-build/build
315317
cp -R /opt/systemd-source/systemd/man ./systemd-build/build
316318
""")

src/main/kotlin/net/sjrx/intellij/plugins/systemdunitfiles/UnitFileIcon.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ object UnitFileIcon {
77
@JvmField
88
val FILE = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/systemd.svg", UnitFileIcon::class.java)
99
val AUTOMOUNT = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/automount.svg", UnitFileIcon::class.java)
10+
val NSPAWN = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/nspawn.svg", UnitFileIcon::class.java)
1011
val DEVICE = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/device.svg", UnitFileIcon::class.java)
1112
val MOUNT = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/mount.svg", UnitFileIcon::class.java)
1213
val PATH = IconLoader.getIcon("/net/sjrx/intellij/plugins/systemdunitfiles/path.svg", UnitFileIcon::class.java)

0 commit comments

Comments
 (0)