diff --git a/src/nimble.nim b/src/nimble.nim index 12706f4..0295903 100644 --- a/src/nimble.nim +++ b/src/nimble.nim @@ -306,7 +306,8 @@ proc buildFromDir(pkgInfo: PackageInfo, paths: seq[string], forRelease: bool) = buildFromDir(pkgInfo, paths, args) proc saveNimbleMeta(pkgDestDir, url, vcsRevision: string, - filesInstalled, bins: HashSet[string]) = + filesInstalled, bins: HashSet[string], + isLink: bool = false) = ## Saves the specified data into a ``nimblemeta.json`` file inside ## ``pkgDestDir``. ## @@ -314,6 +315,9 @@ proc saveNimbleMeta(pkgDestDir, url, vcsRevision: string, ## installed. ## bins - A list of binary filenames which have been installed for this ## package. + ## + ## isLink - Determines whether the installed package is a .nimble-link. + # TODO: Move to packageinstaller.nim var nimblemeta = %{"url": %url} if not vcsRevision.isNil: nimblemeta["vcsRevision"] = %vcsRevision @@ -325,8 +329,20 @@ proc saveNimbleMeta(pkgDestDir, url, vcsRevision: string, nimblemeta["binaries"] = binaries for bin in bins: binaries.add(%bin) + nimblemeta["isLink"] = %isLink writeFile(pkgDestDir / "nimblemeta.json", $nimblemeta) +proc saveNimbleMeta(pkgDestDir, pkgDir, vcsRevision: string) = + ## Overload of saveNimbleMeta for linked (.nimble-link) packages. + ## + ## pkgDestDir - The directory where the package has been installed. + ## For example: ~/.nimble/pkgs/jester-#head/ + ## + ## pkgDir - The directory where the original package files are. + ## For example: ~/projects/jester/ + saveNimbleMeta(pkgDestDir, "file://" & pkgDir, vcsRevision, + initSet[string](), initSet[string](), true) + proc removePkgDir(dir: string, options: Options) = ## Removes files belonging to the package in ``dir``. try: @@ -399,7 +415,6 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, var pkgInfo = getPkgInfo(dir, options) let realDir = pkgInfo.getRealDir() let binDir = options.getBinDir() - let pkgsDir = options.getPkgsDir() var depsOptions = options depsOptions.depsOnly = false @@ -421,12 +436,11 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, # if the build fails then the old package will still be installed. if pkgInfo.bin.len > 0: buildFromDir(pkgInfo, result.paths, true) - let versionStr = '-' & pkgInfo.specialVersion - - let pkgDestDir = pkgsDir / (pkgInfo.name & versionStr) + let pkgDestDir = pkgInfo.getPkgDest(options) if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): - if not options.prompt(pkgInfo.name & versionStr & - " already exists. Overwrite?"): + let msg = "$1@$2 already exists. Overwrite?" % + [pkgInfo.name, pkgInfo.specialVersion] + if not options.prompt(msg): raise NimbleQuit(msg: "") removePkgDir(pkgDestDir, options) # Remove any symlinked binaries @@ -495,49 +509,6 @@ proc installFromDir(dir: string, requestedVer: VersionRange, options: Options, display("Success:", pkgInfo.name & " installed successfully.", Success, HighPriority) -proc getNimbleTempDir(): string = - ## Returns a path to a temporary directory. - ## - ## The returned path will be the same for the duration of the process but - ## different for different runs of it. You have to make sure to create it - ## first. In release builds the directory will be removed when nimble finishes - ## its work. - result = getTempDir() / "nimble_" - when defined(windows): - proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32", - importc: "GetCurrentProcessId".} - result.add($GetCurrentProcessId()) - else: - result.add($getpid()) - -proc downloadPkg(url: string, verRange: VersionRange, - downMethod: DownloadMethod, - options: Options): (string, Version) = - ## Downloads the repository as specified by ``url`` and ``verRange`` using - ## the download method specified. - ## - ## Returns the directory where it was downloaded and the concrete version - ## which was downloaded. - let downloadDir = (getNimbleTempDir() / getDownloadDirName(url, verRange)) - createDir(downloadDir) - var modUrl = - if url.startsWith("git://") and options.config.cloneUsingHttps: - "https://" & url[6 .. ^1] - else: url - - # Fixes issue #204 - # github + https + trailing url slash causes a - # checkout/ls-remote to fail with Repository not found - if modUrl.contains("github.com") and modUrl.endswith("/"): - modUrl = modUrl[0 .. ^2] - - display("Downloading", "$1 using $2" % [modUrl, $downMethod], - priority = HighPriority) - result = ( - downloadDir, - doDownload(modUrl, downloadDir, verRange, downMethod, options) - ) - proc getDownloadInfo*(pv: PkgTuple, options: Options, doPrompt: bool): (DownloadMethod, string) = if pv.name.isURL: @@ -931,6 +902,70 @@ proc listTasks(options: Options) = let nimbleFile = findNimbleFile(getCurrentDir(), true) nimscriptsupport.listTasks(nimbleFile, options) +proc developFromDir(dir: string, options: Options) = + if options.depsOnly: + raiseNimbleError("Cannot develop dependencies only.") + + var pkgInfo = getPkgInfo(dir, options) + if pkgInfo.bin.len > 0: + if "nim" in pkgInfo.skipExt: + raiseNimbleError("Cannot develop packages that are binaries only.") + + display("Warning:", "This package's binaries will not be compiled " & + "nor symlinked for development.", Warning, HighPriority) + + # Overwrite the version to #head always. + pkgInfo.specialVersion = "#head" + + # Dependencies need to be processed before the creation of the pkg dir. + discard processDeps(pkgInfo, options) + + # This is similar to the code in `installFromDir`, except that we + # *consciously* not worry about the package's binaries. + let pkgDestDir = pkgInfo.getPkgDest(options) + if existsDir(pkgDestDir) and existsFile(pkgDestDir / "nimblemeta.json"): + let msg = "$1@$2 already exists. Overwrite?" % + [pkgInfo.name, pkgInfo.specialVersion] + if not options.prompt(msg): + raise NimbleQuit(msg: "") + removePkgDir(pkgDestDir, options) + + createDir(pkgDestDir) + # The .nimble-link file contains the path to the real .nimble file, + # and a secondary path to the source directory of the package. + # The secondary path is necessary so that the package's .nimble file doesn't + # need to be read. This will mean that users will need to re-run + # `nimble develop` if they change their `srcDir` but I think it's a worthy + # compromise. + let contents = pkgInfo.myPath & "\n" & pkgInfo.getRealDir() + writeFile(pkgDestDir / pkgInfo.name.addFileExt("nimble-link"), contents) + + # TODO: Handle dependencies of the package we are developing (they need to be + # installed). + + # Save a nimblemeta.json file. + saveNimbleMeta(pkgDestDir, "file://" & dir, vcsRevisionInDir(dir)) + + # Save the nimble data (which might now contain reverse deps added in + # processDeps). + saveNimbleData(options) + +proc develop(options: Options) = + if options.action.packages == @[]: + developFromDir(getCurrentDir(), options) + else: + # Install each package. + for pv in options.action.packages: + let downloadDir = getCurrentDir() / pv.name + if dirExists(downloadDir): + let msg = "Cannot clone into '$1': directory exists." % downloadDir + let hint = "Remove the directory, or run this command somewhere else." + raiseNimbleError(msg, hint) + + let (meth, url) = getDownloadInfo(pv, options, true) + discard downloadPkg(url, pv.ver, meth, options, downloadDir) + developFromDir(downloadDir, options) + proc execHook(options: Options, before: bool): bool = ## Returns whether to continue. result = true @@ -1002,6 +1037,8 @@ proc doAction(options: Options) = dump(options) of actionTasks: listTasks(options) + of actionDevelop: + develop(options) of actionNil: assert false of actionCustom: diff --git a/src/nimblepkg/download.nim b/src/nimblepkg/download.nim index 1db1096..216c36e 100644 --- a/src/nimblepkg/download.nim +++ b/src/nimblepkg/download.nim @@ -212,6 +212,42 @@ proc doDownload*(url: string, downloadDir: string, verRange: VersionRange, verifyClone() +proc downloadPkg*(url: string, verRange: VersionRange, + downMethod: DownloadMethod, + options: Options, + downloadPath = ""): (string, Version) = + ## Downloads the repository as specified by ``url`` and ``verRange`` using + ## the download method specified. + ## + ## If `downloadPath` isn't specified a location in /tmp/ will be used. + ## + ## Returns the directory where it was downloaded and the concrete version + ## which was downloaded. + let downloadDir = + if downloadPath == "": + (getNimbleTempDir() / getDownloadDirName(url, verRange)) + else: + downloadPath + + createDir(downloadDir) + var modUrl = + if url.startsWith("git://") and options.config.cloneUsingHttps: + "https://" & url[6 .. ^1] + else: url + + # Fixes issue #204 + # github + https + trailing url slash causes a + # checkout/ls-remote to fail with Repository not found + if modUrl.contains("github.com") and modUrl.endswith("/"): + modUrl = modUrl[0 .. ^2] + + display("Downloading", "$1 using $2" % [modUrl, $downMethod], + priority = HighPriority) + result = ( + downloadDir, + doDownload(modUrl, downloadDir, verRange, downMethod, options) + ) + proc echoPackageVersions*(pkg: Package) = let downMethod = pkg.downloadMethod.getDownloadMethod() case downMethod diff --git a/src/nimblepkg/options.nim b/src/nimblepkg/options.nim index eee40cd..5890a00 100644 --- a/src/nimblepkg/options.nim +++ b/src/nimblepkg/options.nim @@ -27,15 +27,16 @@ type actionNil, actionRefresh, actionInit, actionDump, actionPublish, actionInstall, actionSearch, actionList, actionBuild, actionPath, actionUninstall, actionCompile, - actionDoc, actionCustom, actionTasks + actionDoc, actionCustom, actionTasks, actionDevelop Action* = object case typ*: ActionType of actionNil, actionList, actionPublish, actionTasks: nil of actionRefresh: optionalURL*: string # Overrides default package list. - of actionInstall, actionPath, actionUninstall: - packages*: seq[PkgTuple] # Optional only for actionInstall. + of actionInstall, actionPath, actionUninstall, actionDevelop: + packages*: seq[PkgTuple] # Optional only for actionInstall + # and actionDevelop. of actionSearch: search*: seq[string] # Search string. of actionInit, actionDump: @@ -56,6 +57,9 @@ Usage: nimble COMMAND [opts] Commands: install [pkgname, ...] Installs a list of packages. [-d, --depsOnly] Install only dependencies. + develop [pkgname, ...] Clones a list of packages for development. + Symlinks the cloned packages or any package + in the current working directory. init [pkgname] Initializes a new Nimble project. publish Publishes a package on nim-lang/packages. The current working directory needs to be the @@ -141,6 +145,8 @@ proc parseActionType*(action: string): ActionType = result = actionPublish of "tasks": result = actionTasks + of "develop": + result = actionDevelop else: result = actionCustom @@ -149,7 +155,7 @@ proc initAction*(options: var Options, key: string) = ## `key`. let keyNorm = key.normalize() case options.action.typ - of actionInstall, actionPath: + of actionInstall, actionPath, actionDevelop, actionUninstall: options.action.packages = @[] of actionCompile, actionDoc, actionBuild: options.action.compileOptions = @[] @@ -164,8 +170,6 @@ proc initAction*(options: var Options, key: string) = options.action.optionalURL = "" of actionSearch: options.action.search = @[] - of actionUninstall: - options.action.packages = @[] of actionCustom: options.action.command = key options.action.arguments = @[] diff --git a/src/nimblepkg/packageinfo.nim b/src/nimblepkg/packageinfo.nim index 389342d..f854676 100644 --- a/src/nimblepkg/packageinfo.nim +++ b/src/nimblepkg/packageinfo.nim @@ -497,6 +497,11 @@ proc iterInstallFiles*(realDir: string, pkgInfo: PackageInfo, action(file) +proc getPkgDest*(pkgInfo: PackageInfo, options: Options): string = + let versionStr = '-' & pkgInfo.specialVersion + let pkgDestDir = options.getPkgsDir() / (pkgInfo.name & versionStr) + return pkgDestDir + when isMainModule: doAssert getNameVersion("/home/user/.nimble/libs/packagea-0.1") == ("packagea", "0.1") diff --git a/src/nimblepkg/tools.nim b/src/nimblepkg/tools.nim index ebc268d..00e518c 100644 --- a/src/nimblepkg/tools.nim +++ b/src/nimblepkg/tools.nim @@ -141,3 +141,20 @@ proc contains*(j: JsonNode, elem: tuple[key: string, val: JsonNode]): bool = for key, val in pairs(j): if key == elem.key and val == elem.val: return true + +when not defined(windows): + from posix import getpid +proc getNimbleTempDir*(): string = + ## Returns a path to a temporary directory. + ## + ## The returned path will be the same for the duration of the process but + ## different for different runs of it. You have to make sure to create it + ## first. In release builds the directory will be removed when nimble finishes + ## its work. + result = getTempDir() / "nimble_" + when defined(windows): + proc GetCurrentProcessId(): int32 {.stdcall, dynlib: "kernel32", + importc: "GetCurrentProcessId".} + result.add($GetCurrentProcessId()) + else: + result.add($getpid()) \ No newline at end of file diff --git a/tests/develop/binary/binary.nim b/tests/develop/binary/binary.nim new file mode 100644 index 0000000..53c687a --- /dev/null +++ b/tests/develop/binary/binary.nim @@ -0,0 +1 @@ +echo("hello") \ No newline at end of file diff --git a/tests/develop/binary/binary.nimble b/tests/develop/binary/binary.nimble new file mode 100644 index 0000000..b80a50e --- /dev/null +++ b/tests/develop/binary/binary.nimble @@ -0,0 +1,14 @@ +# Package + +version = "1.0" +author = "Dominik Picheta" +description = "binary" +license = "MIT" + +bin = @["binary"] + +skipExt = @["nim"] + +# Dependencies + +requires "nim >= 0.16.0" diff --git a/tests/develop/hybrid/hybrid.nim b/tests/develop/hybrid/hybrid.nim new file mode 100644 index 0000000..53c687a --- /dev/null +++ b/tests/develop/hybrid/hybrid.nim @@ -0,0 +1 @@ +echo("hello") \ No newline at end of file diff --git a/tests/develop/hybrid/hybrid.nimble b/tests/develop/hybrid/hybrid.nimble new file mode 100644 index 0000000..d580fad --- /dev/null +++ b/tests/develop/hybrid/hybrid.nimble @@ -0,0 +1,12 @@ +# Package + +version = "1.0" +author = "Dominik Picheta" +description = "hybrid" +license = "MIT" + +bin = @["hybrid"] + +# Dependencies + +requires "nim >= 0.16.0" diff --git a/tests/develop/srcdirtest/src/srcdirtest.nim b/tests/develop/srcdirtest/src/srcdirtest.nim new file mode 100644 index 0000000..53c687a --- /dev/null +++ b/tests/develop/srcdirtest/src/srcdirtest.nim @@ -0,0 +1 @@ +echo("hello") \ No newline at end of file diff --git a/tests/develop/srcdirtest/srcdirtest.nimble b/tests/develop/srcdirtest/srcdirtest.nimble new file mode 100644 index 0000000..c0f8136 --- /dev/null +++ b/tests/develop/srcdirtest/srcdirtest.nimble @@ -0,0 +1,12 @@ +# Package + +version = "1.0" +author = "Dominik Picheta" +description = "srcdir" +license = "MIT" + +srcDir = "src" + +# Dependencies + +requires "nim >= 0.16.0" diff --git a/tests/tester.nim b/tests/tester.nim index 804a656..603023b 100644 --- a/tests/tester.nim +++ b/tests/tester.nim @@ -460,4 +460,41 @@ test "can pass args with spaces to Nim (#351)": " -d:myVar=\"string with spaces\"" & " binaryPackage") checkpoint output - check exitCode == QuitSuccess \ No newline at end of file + check exitCode == QuitSuccess + +suite "develop feature": + test "can reject binary packages": + cd "develop/binary": + let (output, exitCode) = execNimble("develop") + checkpoint output + check output.processOutput.inLines("cannot develop packages") + check exitCode == QuitFailure + + test "can develop hybrid": + cd "develop/hybrid": + let (output, exitCode) = execNimble("develop") + checkpoint output + check output.processOutput.inLines("will not be compiled") + check exitCode == QuitSuccess + + let path = installDir / "pkgs" / "hybrid-#head" / "hybrid.nimble-link" + check fileExists(path) + let split = readFile(path).splitLines() + check split.len == 2 + check split[0].endsWith("develop/hybrid/hybrid.nimble") + check split[1].endsWith("develop/hybrid") + + test "can develop with srcDir": + cd "develop/srcdirtest": + let (output, exitCode) = execNimble("develop") + checkpoint output + check(not output.processOutput.inLines("will not be compiled")) + check exitCode == QuitSuccess + + let path = installDir / "pkgs" / "srcdirtest-#head" / + "srcdirtest.nimble-link" + check fileExists(path) + let split = readFile(path).splitLines() + check split.len == 2 + check split[0].endsWith("develop/srcdirtest/srcdirtest.nimble") + check split[1].endsWith("develop/srcdirtest/src") \ No newline at end of file