Skip to content

NuGet package support in scriptcs

Adam Ralph edited this page Mar 23, 2015 · 10 revisions

Overview

scriptcs has integrated support for NuGet packages. The integration is used for the following use cases.

  • Using .NET libraries within a script - NuGet is the standard mechanism for publishing .NET libraries. scriptcs users can install most NuGet packages and directly use them within their scripts.
  • Using Script Packs - Script Packs provide a more script-friendly wrapper around other NuGet packages, and remove the need for repetitive boilerplate coding. Script Packs are published as NuGet packages.
  • Using Script Libraries - Script Libraries allow sharing scripts for reuse by publishing them in a package.
  • Using Modules - Modules are an extensibility point in scriptcs that allows extend the scriptcs "runtime" itself.

The scriptcs model is based on a convention over configuration approach. Packages are not explicitly referenced within the script, instead the presence of a package locally means that it is automatically loaded / available.

Package scope

There are 2 locations for packages in scriptcs.

Global

Global packages live in ".\AppData\Local\ScriptCs\Packages". These packages contain scriptcs Modules. For example the FSharpModule is installed globally, and adds the ability for scriptcs to support using F#'s compiler / F# scripts.

Global packages are installed with scriptcs using scriptcs -install -g i.e. scriptcs -install ScriptCs.FSharp -g will install the FSharp module globally.

Loading

Global packages are automatically loaded when scriptcs starts up. We use MEF to scan the global packages for any [IModule] (https://github.com/scriptcs/scriptcs/blob/dev/src/ScriptCs.Contracts/IModule.cs) implementations. Any modules that are found are then invoked so they can configure scriptcs using the supplied module configuration object. Examples

Note: We are planning to change how global packages work in the near future. Once thes changes happen, scriptcs modules will not be installed globally, rather they will be installed at the app level. Global packages will still exist, but will only be used only for supporting CLI commands, similar to how node.js has global NPM packages.

App (Folder) Level

App in this case is a folder with a bunch of scripts. A scriptcs App can have local packages installed. To install them the scriptcs -install command is used specifying the package name. On package installation, a scriptcs_packages.config file will be stored in the script folder. This is the packages.config file using NuGet's standard format. The packages themselves reside in a local scriptcs_packages folder.

Local packages are used for the following reasons:

Loading

At runtime (either in the REPL or running a script) we scan for the local scriptcs_packages.config file. If one is found, then we will look for the scriptcs_packages folder. If that is not found, we will automatically attempt to install the packages. Once the packages are installed (or if they exist) we will then automatically load them into memory. We use MEF to do this. We then use MEF to scan packages for Script Packs. Any that are found are initialized, allowing them to inject imports and references.

Package Installation

As mentioned earlier, packages are installed into scripts using the scriptcs -install command.

  • A user can supply a specific package i.e. scriptcs -install Automapper to have that package installed. scriptcs will automatically install that package and all its dependencies. It will then create a scriptcs_packages.config file if it does not exist.
  • If no package is supplied, then scriptcs will look for the local scriptcs_packages.config file and install all the packages within that file.
  • Users can pass the -g option to install packages globally.
  • Users can pass the -pre option in order to have scriptcs pull pre-release packages. By default it does not.
  • Users can supply a local scriptcs_nuget.config file to override package sources with additional sources like a local folder.

Nuget package resolution / installation

We rely on the NuGet.Core package by default to handle all our NuGet package resolution. We use it to query NuGet to get the packages that need to be installed and to do the actual installation.

Version conflicts / Dynamic Assembly Redirects

One problem we have seen customers get into the past is having their app packages conflict with the versions scriptcs itself uses, or having conflicts within Script Packs due to the depending on an older version of our ScriptCs.Contracts assembly.

To mitigate this we have an AppDomainAssemblyResolver which overrides resolution at the App Domain level and automatically redirects assembly requests at runtime to the last version that is registered with the resolver. This doesn't solve binary conflict problems, but does solve versioning problems.

For example, let's assume a Script Pack (in a package) references ScriptCs.Contracts version 0.10, yet the person is running on ScriptCs version 0.13. At runtime when the Script Pack assembly requests for 0.10, it will get back 0.13 automatically.

Extensiblity

All the services we use in scriptcs to install packages are pluggable and rely on contracts for which the default is NuGet. This gives us the ability to support other providers like Paket.