Skip to content

Commit fd05f30

Browse files
authored
First iteration of the features feature for the declarative parser (#1346)
* First iteration of the `features` feature for the declarative parser There are a few things left to implement. Like adding a special syntax to requires. This PR handles the scenarios for root packages. See the `tdeclarativeparser` tests over the `features` packages. Nimble file ```nim # Dependencies requires "nim >= 2.3.1" feature "feature1": requires "stew" ```nim when defined(features.features.feature1): echo "feature1 is enabled" import stew/byteutils #we should be able to import stew here as is its part of the feature1 else: echo "feature1 is disabled" ``` * dont require `nim` devel in test * Removes log
1 parent afd9298 commit fd05f30

File tree

10 files changed

+147
-14
lines changed

10 files changed

+147
-14
lines changed

src/nimble.nim

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ proc processFreeDependenciesSAT(rootPkgInfo: PackageInfo, options: Options): Has
8787
var pkgsToInstall: seq[(string, Version)] = @[]
8888
var rootPkgInfo = rootPkgInfo
8989
rootPkgInfo.requires &= options.extraRequires
90+
if options.useDeclarativeParser:
91+
rootPkgInfo = rootPkgInfo.toRequiresInfo(options)
92+
# displayInfo(&"Features: options: {options.features} pkg: {rootPkgInfo.features}", HighPriority)
93+
for feature in options.features:
94+
if feature in rootPkgInfo.features:
95+
rootPkgInfo.requires &= rootPkgInfo.features[feature]
96+
# displayInfo(&"Feature {feature} activated", LowPriority)
97+
9098
var pkgList = initPkgList(rootPkgInfo, options)
9199
if options.useDeclarativeParser:
92100
pkgList = pkgList.mapIt(it.toRequiresInfo(options))
@@ -290,6 +298,10 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: HashSet[seq[string]],
290298
# Disable coloured output
291299
args.add("--colors:off")
292300

301+
for feature in options.features:
302+
let featureStr = &"features.{pkgInfo.basicInfo.name}.{feature}"
303+
args.add &"-d:{featureStr}"
304+
293305
let binToBuild =
294306
# Only build binaries specified by user if any, but only if top-level package,
295307
# dependencies should have every binary built.
@@ -1802,7 +1814,8 @@ proc validateParsedDependencies(pkgInfo: PackageInfo, options: Options) =
18021814

18031815
options.useDeclarativeParser = false
18041816
let vmDeps = pkgInfo.toFullInfo(options).requires
1805-
1817+
displayInfo(&"Parsed declarative dependencies: {declDeps}", HighPriority)
1818+
displayInfo(&"Parsed VM dependencies: {vmDeps}", HighPriority)
18061819
if declDeps != vmDeps:
18071820
raise nimbleError(&"Parsed declarative and VM dependencies are not the same: {declDeps} != {vmDeps}")
18081821

src/nimblepkg/declarativeparser.nim

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,31 @@ import std/strutils
55

66
import compiler/[ast, idents, msgs, syntaxes, options, pathutils, lineinfos]
77
import version, packageinfotypes, packageinfo, options, packageparser
8+
import std/[tables, sequtils]
89

910
type NimbleFileInfo* = object
1011
requires*: seq[string]
1112
srcDir*: string
1213
version*: string
1314
tasks*: seq[(string, string)]
15+
features*: Table[string, seq[string]]
1416
hasInstallHooks*: bool
1517
hasErrors*: bool
1618

1719
proc eqIdent(a, b: string): bool {.inline.} =
1820
cmpIgnoreCase(a, b) == 0 and a[0] == b[0]
1921

22+
proc extractRequires(n: PNode, conf: ConfigRef, result: var seq[string], hasErrors: var bool) =
23+
for i in 1 ..< n.len:
24+
var ch: PNode = n[i]
25+
while ch.kind in {nkStmtListExpr, nkStmtList} and ch.len > 0:
26+
ch = ch.lastSon
27+
if ch.kind in {nkStrLit .. nkTripleStrLit}:
28+
result.add ch.strVal
29+
else:
30+
localError(conf, ch.info, "'requires' takes string literals")
31+
hasErrors = true
32+
2033
proc extract(n: PNode, conf: ConfigRef, result: var NimbleFileInfo) =
2134
case n.kind
2235
of nkStmtList, nkStmtListExpr:
@@ -26,15 +39,19 @@ proc extract(n: PNode, conf: ConfigRef, result: var NimbleFileInfo) =
2639
if n[0].kind == nkIdent:
2740
case n[0].ident.s
2841
of "requires":
29-
for i in 1 ..< n.len:
30-
var ch = n[i]
31-
while ch.kind in {nkStmtListExpr, nkStmtList} and ch.len > 0:
32-
ch = ch.lastSon
33-
if ch.kind in {nkStrLit .. nkTripleStrLit}:
34-
result.requires.add ch.strVal
35-
else:
36-
localError(conf, ch.info, "'requires' takes string literals")
37-
result.hasErrors = true
42+
extractRequires(n, conf, result.requires, result.hasErrors)
43+
of "feature":
44+
if n.len >= 3 and n[1].kind in {nkStrLit .. nkTripleStrLit}:
45+
let featureName = n[1].strVal
46+
if not result.features.hasKey(featureName):
47+
result.features[featureName] = @[]
48+
if n[2].kind in {nkStmtList, nkStmtListExpr}:
49+
for stmt in n[2]:
50+
if stmt.kind in nkCallKinds and stmt[0].kind == nkIdent and
51+
stmt[0].ident.s == "requires":
52+
var requires: seq[string]
53+
extractRequires(stmt, conf, requires, result.hasErrors)
54+
result.features[featureName].add requires
3855
of "task":
3956
if n.len >= 3 and n[1].kind == nkIdent and
4057
n[2].kind in {nkStrLit .. nkTripleStrLit}:
@@ -170,16 +187,23 @@ proc getRequires*(nimbleFileInfo: NimbleFileInfo): seq[PkgTuple] =
170187
for require in nimbleFileInfo.requires:
171188
result.add(parseRequires(require))
172189

190+
proc getFeatures*(nimbleFileInfo: NimbleFileInfo): Table[string, seq[PkgTuple]] =
191+
result = initTable[string, seq[PkgTuple]]()
192+
for feature, requires in nimbleFileInfo.features:
193+
result[feature] = requires.map(parseRequires)
194+
173195
proc toRequiresInfo*(pkgInfo: PackageInfo, options: Options): PackageInfo =
174196
#For nim we only need the version. Since version is usually in the form of `version = $NimMajor & "." & $NimMinor & "." & $NimPatch
175197
#we need to use the vm to get the version. Another option could be to use the binary and ask for the version
176198
if pkgInfo.basicInfo.name.isNim:
177199
return pkgInfo.toFullInfo(options)
178-
200+
179201
let nimbleFileInfo = extractRequiresInfo(pkgInfo.myPath)
180202
result = pkgInfo
181203
result.requires = getRequires(nimbleFileInfo)
182-
result.infoKind = pikRequires
204+
if pkgInfo.infoKind != pikFull: #dont update as full implies pik requires
205+
result.infoKind = pikRequires
206+
result.features = getFeatures(nimbleFileInfo)
183207

184208
when isMainModule:
185209
for x in tokenizeRequires("jester@#head >= 1.5 & <= 1.8"):

src/nimblepkg/nimscriptapi.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,6 @@ proc getPaths*(): seq[string] =
255255
proc getPathsClause*(): string =
256256
## Returns the paths to the dependencies as consumed by the nim compiler.
257257
return getPaths().mapIt("--path:" & it).join(" ")
258+
259+
template feature*(name: string, body: untyped): untyped =
260+
discard

src/nimblepkg/options.nim

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ type
6565
disableNimBinaries*: bool # Whether to disable the use of nim binaries
6666
maxTaggedVersions*: int # Maximum number of tags to check for a package when discovering versions in a local repo
6767
useDeclarativeParser*: bool # Whether to use the declarative parser for parsing nimble files (only when solver is SAT)
68+
features*: seq[string] # Features to be activated. Only used when using the declarative parser
6869

6970
ActionType* = enum
7071
actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionUpgrade
@@ -278,6 +279,7 @@ Nimble Options:
278279
--disableNimBinaries Disable the use of nim precompiled binaries. Note in some platforms precompiled binaries are not available but the flag can still be used to avoid compile the Nim version once and reuse it.
279280
--maximumTaggedVersions Maximum number of tags to check for a package when discovering versions for the SAT solver. 0 means all.
280281
--parser:declarative|nimvm Use the declarative parser or the nimvm parser (default).
282+
--features Activate features. Only used when using the declarative parser.
281283
For more information read the GitHub readme:
282284
https://github.com/nim-lang/nimble#readme
283285
"""
@@ -680,6 +682,8 @@ proc parseFlag*(flag, val: string, result: var Options, kind = cmdLongOption) =
680682
result.maxTaggedVersions = parseUInt(val).int
681683
except ValueError:
682684
raise nimbleError(&"{val} is not a valid value")
685+
of "features":
686+
result.features = val.split(";").mapIt(it.strip)
683687
else: isGlobalFlag = false
684688

685689
var wasFlagHandled = true

src/nimblepkg/packageinfotypes.nim

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ type
7777
isLink*: bool
7878
paths*: seq[string]
7979
entryPoints*: seq[string] #useful for tools like the lsp.
80+
features*: Table[string, seq[PkgTuple]] #features requires defined in the nimble file. Declarative parser + SAT solver only
8081

8182
Package* = object ## Definition of package from packages.json.
8283
# Required fields in a package.

tests/features/features.nimble

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Package
2+
3+
version = "0.1.0"
4+
author = "jmgomez"
5+
description = "A new awesome nimble package"
6+
license = "MIT"
7+
srcDir = "src"
8+
bin = @["features"]
9+
10+
# Dependencies
11+
12+
requires "nim"
13+
14+
feature "feature1":
15+
requires "stew"

tests/features/src/features.nim

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# This is just an example to get you started. A typical library package
2+
# exports the main API in this file. Note that you cannot rename this file
3+
# but you can remove it if you wish.
4+
5+
proc add*(x, y: int): int =
6+
## Adds two numbers together.
7+
return x + y
8+
9+
10+
11+
when defined(features.features.feature1):
12+
echo "feature1 is enabled"
13+
import stew/byteutils #we should be able to import stew here as is its part of the feature1
14+
15+
else:
16+
echo "feature1 is disabled"
17+
18+
echo ""
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This is just an example to get you started. Users of your library will
2+
# import this file by writing ``import features/submodule``. Feel free to rename or
3+
# remove this file altogether. You may create additional modules alongside
4+
# this file as required.
5+
6+
type
7+
Submodule* = object
8+
name*: string
9+
10+
proc initSubmodule*(): Submodule =
11+
## Initialises a new ``Submodule`` object.
12+
Submodule(name: "Anonymous")

tests/features/tests/test1.nim

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# This is just an example to get you started. You may wish to put all of your
2+
# tests into a single file, or separate them into multiple `test1`, `test2`
3+
# etc. files (better names are recommended, just make sure the name starts with
4+
# the letter 't').
5+
#
6+
# To run these tests, simply execute `nimble test`.
7+
8+
import unittest
9+
10+
import features
11+
test "can add":
12+
check add(5, 5) == 10

tests/tdeclarativeparser.nim

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import unittest
33
import testscommon
44
import std/[options, tables, sequtils, os]
55
import
6-
nimblepkg/[packageinfotypes, version, options, config, nimblesat, declarativeparser, cli]
6+
nimblepkg/[packageinfotypes, version, options, config, nimblesat, declarativeparser, cli, common]
77

88
proc getNimbleFileFromPkgNameHelper(pkgName: string): string =
99
let pv: PkgTuple = (pkgName, VersionRange(kind: verAny))
@@ -72,4 +72,35 @@ suite "Declarative parsing":
7272
check exitCode == QuitSuccess
7373

7474

75-
# suite "Declarative parser features":
75+
suite "Declarative parser features":
76+
test "should be able to parse features from a nimble file":
77+
let nimbleFile = "./features/features.nimble"
78+
let nimbleFileInfo = extractRequiresInfo(nimbleFile)
79+
let features = nimbleFileInfo.features
80+
check features.len == 1
81+
check features["feature1"] == @["stew"]
82+
83+
test "should be able to install a package using the declarative parser with a feature":
84+
cd "features":
85+
#notice it imports stew, which will fail to compile if feature1 is not activated although it only imports it in the when part
86+
let (output, exitCode) = execNimble("--parser:declarative", "--features:feature1", "run")
87+
check exitCode == QuitSuccess
88+
check output.processOutput.inLines("feature1 is enabled")
89+
90+
test "should not enable features if not specified":
91+
cd "features":
92+
let (output, exitCode) = execNimble("run")
93+
check exitCode == QuitSuccess
94+
check output.processOutput.inLines("feature1 is disabled")
95+
96+
97+
#[NEXT Tests:
98+
99+
TODO:
100+
- compile time nimble parser detection so we can warn when using the vm parser with features
101+
- add enable features to nimble.paths
102+
- dependencies syntax i.e. requires "stew > 0.3.4[feature1; feature2]"
103+
104+
105+
]#
106+

0 commit comments

Comments
 (0)