diff --git a/.github/workflows/docker-cd.yml b/.github/workflows/docker-cd.yml new file mode 100644 index 00000000..e1ed4155 --- /dev/null +++ b/.github/workflows/docker-cd.yml @@ -0,0 +1,41 @@ +name: Docker CD + +on: + push: + branches: + - master + tags: + - '*' + paths-ignore: + - '**.md' + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + + - name: checkout code + uses: actions/checkout@v2 + + - name: Log in to ghcr + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - if: startsWith(github.ref, 'refs/heads/master') + run: echo "TAG=latest" >> $GITHUB_ENV + + - if: startsWith(github.ref, 'refs/tags') + run: echo "TAG=$(git describe --tags)" >> $GITHUB_ENV + + - name: Build & Push + uses: docker/build-push-action@v2 + with: + context: ./build + file: ./build/Dockerfile + push: true + tags: ghcr.io/${{ github.repository }}:${{ env.TAG }} \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..1072bdeb --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,62 @@ +name: CI + +on: [push, pull_request] + +jobs: + build-linux: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Install .Net Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 9.0.x + include-prerelease: true + - name: Install dotnet-script + run: dotnet tool install dotnet-script --global + + - name: Run build script + run: dotnet-script build/Build.csx + + build-mac: + runs-on: macos-13 + + steps: + - uses: actions/checkout@v3 + - name: Install .Net Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 9.0.x + include-prerelease: true + - name: Install dotnet-script + run: dotnet tool install dotnet-script --global + + - name: Run build script + run: dotnet-script build/Build.csx + + build-windows: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + - name: Install .Net Core + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 9.0.x + include-prerelease: true + - name: Install dotnet-script + run: dotnet tool install dotnet-script --global + + - name: Run build script + run: dotnet-script build/Build.csx + env: # Or as an environment variable + GITHUB_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + IS_SECURE_BUILDENVIRONMENT: ${{ secrets.IS_SECURE_BUILDENVIRONMENT }} + NUGET_APIKEY: ${{ secrets.NUGET_APIKEY }} diff --git a/.gitignore b/.gitignore index f9797127..62f574c6 100644 --- a/.gitignore +++ b/.gitignore @@ -22,10 +22,12 @@ bld/ [Oo]bj/ [Ll]og/ build/Chocolatey/*.nuspec -build/Chocolatey/tools/LICENSE.TXT build/.vscode build/Artifacts coverage.info +# Ignore these since we download dotnet-script into the root folder on the build server. +dotnet-script +dotnet-script.zip # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot @@ -263,3 +265,5 @@ project.json /build/dotnet-script /dotnet-script /.vscode +/src/Dotnet.Script/Properties/launchSettings.json +.DS_Store diff --git a/Dotnet.Script.sln b/Dotnet.Script.sln index 43cd597b..ea675183 100644 --- a/Dotnet.Script.sln +++ b/Dotnet.Script.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2005 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31129.286 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dotnet.Script", "src\Dotnet.Script\Dotnet.Script.csproj", "{057F56AD-AF29-4ABD-B6DE-26E4DEE169F7}" EndProject @@ -14,9 +14,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dotnet.Script.DependencyMod EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dotnet.Script.DependencyModel.NuGet", "src\Dotnet.Script.DependencyModel.Nuget\Dotnet.Script.DependencyModel.NuGet.csproj", "{E361528F-178A-4489-AF01-FFD3A7122D99}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dotnet.Script.Desktop.Tests", "src\Dotnet.Script.Desktop.Tests\Dotnet.Script.Desktop.Tests.csproj", "{B0E5959C-6E7F-4023-96BA-5AD4A5E00541}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dotnet.Script.Desktop.Tests", "src\Dotnet.Script.Desktop.Tests\Dotnet.Script.Desktop.Tests.csproj", "{B0E5959C-6E7F-4023-96BA-5AD4A5E00541}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dotnet.Script.Shared.Tests", "src\Dotnet.Script.Shared.Tests\Dotnet.Script.Shared.Tests.csproj", "{8FFA2816-411E-437C-AB75-FFA546780E7A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dotnet.Script.Shared.Tests", "src\Dotnet.Script.Shared.Tests\Dotnet.Script.Shared.Tests.csproj", "{8FFA2816-411E-437C-AB75-FFA546780E7A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F20002BB-970C-4318-B618-58D478CAC405}" EndProject diff --git a/README.md b/README.md index 5ebdbfc0..3482c176 100644 --- a/README.md +++ b/README.md @@ -6,26 +6,28 @@ Run C# scripts from the .NET CLI, define NuGet packages inline and edit/debug th [![Build Status](https://bernhardrichter.visualstudio.com/dotnet-script/_apis/build/status/filipw.dotnet-script?branchName=master)](https://bernhardrichter.visualstudio.com/dotnet-script/_build/latest?definitionId=4&branchName=master) +## NuGet Packages -## Nuget Packages - -| Name | Version | Framework | -|---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------| -| `dotnet-script` | [![Nuget](http://img.shields.io/nuget/v/dotnet-script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet-script/) | `netcoreapp2.1` | -| `Dotnet.Script` | [![Nuget](http://img.shields.io/nuget/v/dotnet.script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet.script/) | `netcoreapp2.1` | -| `Dotnet.Script.Core` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.Core.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.Core/) | `netstandard2.0` | -| `Dotnet.Script.DependencyModel` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.DependencyModel.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.DependencyModel/) | `netstandard2.0` | -| `Dotnet.Script.DependencyModel.Nuget` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.DependencyModel.Nuget.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.DependencyModel.Nuget/) | `netstandard2.0` | +| Name | Version | Framework(s) | +| ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | +| `dotnet-script` (global tool) | [![Nuget](http://img.shields.io/nuget/v/dotnet-script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet-script/) | `net8.0`,`net9.0` | +| `Dotnet.Script` (CLI as Nuget) | [![Nuget](http://img.shields.io/nuget/v/dotnet.script.svg?maxAge=10800)](https://www.nuget.org/packages/dotnet.script/) | `net8.0`,`net9.0` | +| `Dotnet.Script.Core` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.Core.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.Core/) | `net8.0`,`net9.0`,`netstandard2.0` | +| `Dotnet.Script.DependencyModel` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.DependencyModel.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.DependencyModel/) | `netstandard2.0` | +| `Dotnet.Script.DependencyModel.Nuget` | [![Nuget](http://img.shields.io/nuget/v/Dotnet.Script.DependencyModel.Nuget.svg?maxAge=10800)](https://www.nuget.org/packages/Dotnet.Script.DependencyModel.Nuget/) | `netstandard2.0` | ## Installing ### Prerequisites -The only thing we need to install is [.Net Core 2.1+ SDK](https://www.microsoft.com/net/download/core). +The only thing we need to install is [.NET 8.0 or .NET 9.0 SDK](https://dotnet.microsoft.com/en-us/download/dotnet). + +[Note](https://learn.microsoft.com/en-us/dotnet/core/install/linux-scripted-manual#manual-install): +> If you install the .NET SDK to a non-default location, you need to set the environment variable `DOTNET_ROOT` to the directory that contains the dotnet executable -### .Net Core 2.1 Global Tool +### .NET Core Global Tool -.Net Core 2.1 introduces the concept of global tools meaning that you can install `dotnet-script` using nothing but the .NET CLI. +.NET Core 2.1 introduced the concept of global tools meaning that you can install `dotnet-script` using nothing but the .NET CLI. ```shell dotnet tool install -g dotnet-script @@ -35,9 +37,6 @@ Tool 'dotnet-script' (version '0.22.0') was successfully installed. ``` The advantage of this approach is that you can use the same command for installation across all platforms. - -> ⚠️ In order to use the global tool you need [.Net Core SDK 2.1.300](https://www.microsoft.com/net/download/dotnet-core/sdk-2.1.300) or higher. The earlier previews and release candidates of .NET Core 2.1 are not supported. - .NET Core SDK also supports viewing a list of installed tools and their uninstallation. ```shell @@ -56,26 +55,22 @@ Tool 'dotnet-script' (version '0.22.0') was successfully uninstalled. ### Windows -```powershell -choco install dotnet.script -``` - -We also provide a PowerShell script for installation. +PowerShell script for installation. ```powershell -(new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.ps1") | iex +(new-object Net.WebClient).DownloadString("https://raw.githubusercontent.com/dotnet-script/dotnet-script/master/install/install.ps1") | iex ``` ### Linux and Mac ```shell -curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | bash +curl -s https://raw.githubusercontent.com/dotnet-script/dotnet-script/master/install/install.sh | bash ``` If permission is denied we can try with `sudo` ```shell -curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | sudo bash +curl -s https://raw.githubusercontent.com/dotnet-script/dotnet-script/master/install/install.sh | sudo bash ``` ### Docker @@ -95,8 +90,7 @@ docker run -it dotnet-script --version ### Github -You can manually download all the releases in `zip` format from the [Github releases page](https://github.com/filipw/dotnet-script/releases). - +You can manually download all the releases in `zip` format from the [GitHub releases page](https://github.com/dotnet-script/dotnet-script/releases). ## Usage @@ -112,6 +106,19 @@ That is all it takes and we can execute the script. Args are accessible via the dotnet script helloworld.csx ``` +The following namespaces are available in the script implicitly and do not need to be imported explicitly: + + - System + - System.IO + - System.Collections.Generic + - System.Console + - System.Diagnostics + - System.Dynamic + - System.Linq + - System.Linq.Expressions + - System.Text + - System.Threading.Tasks + ### Scaffolding Simply create a folder somewhere on your system and issue the following command. @@ -149,15 +156,24 @@ Instead of `main.csx` which is the default, we now have a file named `custom.csx > Note: Executing `dotnet script init` inside a folder that already contains one or more script files will not create the `main.csx` file. ### Running scripts -Scripts can be executed directly from the shell as if they were executables. + +You can execute your script using **dotnet script** or **dotnet-script**. + +```bash +dotnet script foo.csx +dotnet-script foo.csx +``` + +On OSX/Linux, scripts can be executed directly from a shell as if they were executables. ```bash foo.csx arg1 arg2 arg3 ``` + > OSX/Linux > -> Just like all scripts, on OSX/Linux you need to have a !# and mark the file as executable via **chmod +x foo.csx**. -> If you use **dotnet script init** to create your csx it will automatically have the !# directive and be marked as +> Just like all scripts, on OSX/Linux you need to have a `#!` and mark the file as executable via **chmod +x foo.csx**. +> If you use **dotnet script init** to create your csx it will automatically have the `#!` directive and be marked as > executable. The OSX/Linux shebang directive should be **#!/usr/bin/env dotnet-script** @@ -167,7 +183,9 @@ The OSX/Linux shebang directive should be **#!/usr/bin/env dotnet-script** Console.WriteLine("Hello world"); ``` -You can execute your script using **dotnet script** or **dotnet-script**, which allows you to pass arguments to control your script execution more. +On Windows, you can run `dotnet script register` to achieve a similar behaviour. This registers dotnet-script in the Windows registry as the tool to process .csx files. + +You can pass arguments to control your script execution more. ```bash foo.csx arg1 arg2 arg3 @@ -175,8 +193,6 @@ dotnet script foo.csx -- arg1 arg2 arg3 dotnet-script foo.csx -- arg1 arg2 arg3 ``` - - #### Passing arguments to scripts All arguments after `--` are passed to the script in the following way: @@ -233,18 +249,19 @@ dotnet script foo.csx -s https://SomePackageSource -s https://AnotherPackageSour ``` ### Creating DLLs or Exes from a CSX file + Dotnet-Script can create a standalone executable or DLL for your script. -| Switch | Long switch | description | -|--------|---------------------------------|----------------------------------------------------------------------------------------------------------------------| -| -o | --output | Directory where the published executable should be placed. Defaults to a 'publish' folder in the current directory. | -| -n | --name | The name for the generated DLL (executable not supported at this time). Defaults to the name of the script. | -| | --dll | Publish to a .dll instead of an executable. | -| -c | --configuration | Configuration to use for publishing the script [Release/Debug]. Default is "Debug" | -| -d | --debug | Enables debug output. | -| -r | --runtime | The runtime used when publishing the self contained executable. Defaults to your current runtime. | +| Switch | Long switch | description | +| ------ | ------------------------------- | ------------------------------------------------------------------------------------------------------------------- | +| -o | --output | Directory where the published executable should be placed. Defaults to a 'publish' folder in the current directory. | +| -n | --name | The name for the generated DLL (executable not supported at this time). Defaults to the name of the script. | +| | --dll | Publish to a .dll instead of an executable. | +| -c | --configuration | Configuration to use for publishing the script [Release/Debug]. Default is "Debug" | +| -d | --debug | Enables debug output. | +| -r | --runtime | The runtime used when publishing the self contained executable. Defaults to your current runtime. | -The executable you can run directly independent of dotnet install, while the DLL is can be run using the dotnet CLI like this: +The executable you can run directly independent of dotnet install, while the DLL can be run using the dotnet CLI like this: ```shell dotnet script exec {path_to_dll} -- arg1 arg2 @@ -263,7 +280,16 @@ This is an out-of-process operation and represents a significant overhead to the In order to execute a script it needs to be compiled first and since that is a CPU and time consuming operation, we make sure that we only compile when the source code has changed. This works by creating a SHA256 hash from all the script files involved in the execution. This hash is written to a temporary location along with the DLL that represents the result of the script compilation. When a script is executed the hash is computed and compared with the hash from the previous compilation. If they match there is no need to recompile and we run from the already compiled DLL. If the hashes don't match, the cache is invalidated and we recompile. -> You can override this automatic caching by passing **--nocache** flag, which will bypass both caches and cause dependency resolution and script compilation to happen every time we execute the script. +> You can override this automatic caching by passing **--no-cache** flag, which will bypass both caches and cause dependency resolution and script compilation to happen every time we execute the script. + +#### Cache Location + +The temporary location used for caches is a sub-directory named `dotnet-script` under (in order of priority): + +1. The path specified for the value of the environment variable named `DOTNET_SCRIPT_CACHE_LOCATION`, if defined and value is not empty. +2. Linux distributions only: `$XDG_CACHE_HOME` if defined otherwise `$HOME/.cache` +3. macOS only: `~/Library/Caches` +4. The value returned by [`Path.GetTempPath`](https://docs.microsoft.com/en-us/dotnet/api/system.io.path.gettemppath) for the platform. ### @@ -272,6 +298,7 @@ In order to execute a script it needs to be compiled first and since that is a C The days of debugging scripts using `Console.WriteLine` are over. One major feature of `dotnet script` is the ability to debug scripts directly in VS Code. Just set a breakpoint anywhere in your script file(s) and hit F5(start debugging) ![debug](https://user-images.githubusercontent.com/1034073/30173509-2f31596c-93f8-11e7-9124-ca884cf6564e.gif) + ### Script Packages Script packages are a way of organizing reusable scripts into NuGet packages that can be consumed by other scripts. This means that we now can leverage scripting infrastructure without the need for any kind of bootstrapping. @@ -302,12 +329,11 @@ If the entry point script cannot be determined, we will simply load all the scri #### Consuming a script package -To consume a script package all we need to do specify the NuGet package in the `#load `directive. +To consume a script package all we need to do specify the NuGet package in the `#load`directive. The following example loads the [simple-targets](https://www.nuget.org/packages/simple-targets-csx) package that contains script files to be included in our script. ```C# -#! "netcoreapp2.1" #load "nuget:simple-targets-csx, 6.0.0" using static SimpleTargets; @@ -320,8 +346,6 @@ Run(Args, targets); > Note: Debugging also works for script packages so that we can easily step into the scripts that are brought in using the `#load` directive. - - ### Remote Scripts Scripts don't actually have to exist locally on the machine. We can also execute scripts that are made available on an `http(s)` endpoint. @@ -353,8 +377,6 @@ public static string GetScriptFolder([CallerFilePath] string path = null) => Pat > Tip: Put these methods as top level methods in a separate script file and `#load` that file wherever access to the script path and/or folder is needed. - - ## REPL This release contains a C# REPL (Read-Evaluate-Print-Loop). The REPL mode ("interactive mode") is started by executing `dotnet-script` without any arguments. @@ -388,7 +410,7 @@ List(2) { "foo", "bar" } ### Inline Nuget packages -REPL also supports inline Nuget packages - meaning the Nuget packages can be installed into the REPL from *within the REPL*. This is done via our `#r` and `#load` from Nuget support and uses identical syntax. +REPL also supports inline Nuget packages - meaning the Nuget packages can be installed into the REPL from _within the REPL_. This is done via our `#r` and `#load` from Nuget support and uses identical syntax. ``` ~$ dotnet script @@ -419,7 +441,7 @@ Using Roslyn syntax parsing, we also support multiline REPL mode. This means tha Aside from the regular C# script code, you can invoke the following commands (directives) from within the REPL: | Command | Description | -|----------|--------------------------------------------------------------| +| -------- | ------------------------------------------------------------ | | `#load` | Load a script into the REPL (same as `#load` usage in CSX) | | `#r` | Load an assembly into the REPL (same as `#r` usage in CSX) | | `#reset` | Reset the REPL back to initial state (without restarting it) | @@ -461,7 +483,6 @@ The following example shows how we can pipe data in and out of a script. The `UpperCase.csx` script simply converts the standard input to upper case and writes it back out to standard output. ```csharp -#! "netcoreapp2.1" using (var streamReader = new StreamReader(Console.OpenStandardInput())) { Write(streamReader.ReadToEnd().ToUpper()); @@ -488,7 +509,7 @@ The first thing we need to do add the following to the `launch.config` file that } ``` -To debug this script we need a way to attach the debugger in VS Code and to the simplest thing we can do here is to wait for the debugger to attach by adding this method somewhere. +To debug this script we need a way to attach the debugger in VS Code and the simplest thing we can do here is to wait for the debugger to attach by adding this method somewhere. ```c# public static void WaitForDebugger() @@ -502,15 +523,13 @@ public static void WaitForDebugger() To debug the script when executing it from the command line we can do something like -````c# -#! "netcoreapp2.0" -#r "nuget: NetStandard.Library, 2.0.0" +```c# WaitForDebugger(); using (var streamReader = new StreamReader(Console.OpenStandardInput())) { Write(streamReader.ReadToEnd().ToUpper()); // <- SET BREAKPOINT HERE } -```` +``` Now when we run the script from the command line we will get @@ -519,9 +538,9 @@ $ echo "This is some text" | dotnet script UpperCase.csx Attach Debugger (VS Code) ``` -This now gives us a chance to attach the debugger before stepping into the script and from VS Code, select the `.NET Core Attach` debugger and pick the process that represents the executing script. +This now gives us a chance to attach the debugger before stepping into the script and from VS Code, select the `.NET Core Attach` debugger and pick the process that represents the executing script. -Once that is done we should see out breakpoint being hit. +Once that is done we should see our breakpoint being hit. ## Configuration(Debug/Release) @@ -535,11 +554,57 @@ We can specify this when executing the script. dotnet script foo.csx -c release ``` +## + +## Nullable reference types + +Starting from version 0.50.0, `dotnet-script` supports .Net Core 3.0 and all the C# 8 features. +The way we deal with [nullable references types](https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references) in `dotnet-script` is that we turn every warning related to nullable reference types into compiler errors. This means every warning between `CS8600` and `CS8655` are treated as an error when compiling the script. + +Nullable references types are turned off by default and the way we enable it is using the `#nullable enable` compiler directive. This means that existing scripts will continue to work, but we can now opt-in on this new feature. + +```csharp +#!/usr/bin/env dotnet-script + +#nullable enable + +string name = null; +``` + +Trying to execute the script will result in the following error + +```shell +main.csx(5,15): error CS8625: Cannot convert null literal to non-nullable reference type. +``` + +We will also see this when working with scripts in VS Code under the problems panel. + +![image](https://user-images.githubusercontent.com/1034073/65727087-0e982600-e0b7-11e9-8fa0-d16331ab948a.png) + +## Specifying an SDK + +Starting with `dotnet-script` 1.4.0 we can now specify the SDK to be used for a script. + +For instance, creating a web server in a script is now as simple as the following. + +```csharp +#r "sdk:Microsoft.NET.Sdk.Web" + +using Microsoft.AspNetCore.Builder; + +var a = WebApplication.Create(); +a.MapGet("/", () => "Hello world"); +a.Run(); +``` + +> Please note the the only SDK currently supported is `Microsoft.NET.Sdk.Web` + ## Team -* [Bernhard Richter](https://github.com/seesharper) ([@bernhardrichter](https://twitter.com/bernhardrichter)) -* [Filip W](https://github.com/filipw) ([@filip_woj](https://twitter.com/filip_woj)) +- [Bernhard Richter](https://github.com/seesharper) ([@bernhardrichter](https://twitter.com/bernhardrichter)) +- [Filip W](https://github.com/filipw) ([@filip_woj](https://twitter.com/filip_woj)) ## License -[MIT License](https://github.com/filipw/dotnet-script/blob/master/LICENSE) +[MIT License](https://github.com/dotnet-script/dotnet-script/blob/master/LICENSE) + - diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 44693325..00000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,96 +0,0 @@ -resources: -- repo: self - -jobs: - -- job: Job_3 - displayName: Ubuntu Agent - condition: succeeded() - pool: - name: Hosted Ubuntu 1604 - steps: - - bash: 'curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -version 3.0.100-preview-010184' - displayName: 'Install 3.0.100-preview-010184' - - - bash: 'curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -version 2.1.402' - displayName: 'Install 2.1.402' - - - bash: | - export PATH=/home/vsts/.dotnet:$PATH - curl -L https://github.com/filipw/dotnet-script/releases/download/0.28.0/dotnet-script.0.28.0.zip > dotnet-script.zip - unzip -o dotnet-script.zip -d ./ - displayName: 'Install dotnet-script' - - - bash: | - export PATH=/home/vsts/.dotnet:$PATH - dotnet dotnet-script/dotnet-script.dll build/Build.csx - displayName: 'Run build.csx' - - -- job: Job_1 - displayName: Mac Agent - condition: succeeded() - pool: - name: Hosted macOS - steps: - - bash: | - curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin -version 3.0.100-preview-010184 - - displayName: 'Install 3.0.100-preview-010184' - - - bash: | - curl -L https://github.com/filipw/dotnet-script/releases/download/0.28.0/dotnet-script.0.28.0.zip > dotnet-script.zip - unzip -o dotnet-script.zip -d ./ - displayName: 'Install dotnet-script' - - - bash: 'dotnet dotnet-script/dotnet-script.dll build/build.csx' - displayName: 'Run build.csx' - - -- job: Job_2 - displayName: Windows Agent - condition: succeeded() - pool: - name: Hosted Windows 2019 with VS2019 - steps: - - powershell: | - iwr https://raw.githubusercontent.com/dotnet/cli/release/2.1.3xx/scripts/obtain/dotnet-install.ps1 -outfile dotnet-install.ps1 - .\dotnet-install.ps1 -Version 3.0.100-preview-010184 - - displayName: 'Install 3.0.100-preview-010184 SDK' - - - powershell: | - iwr https://raw.githubusercontent.com/dotnet/cli/release/2.1.3xx/scripts/obtain/dotnet-install.ps1 -outfile dotnet-install.ps1 - .\dotnet-install.ps1 -Version 2.1.402 - - displayName: 'Install 3.0.100-preview-010184 2.1.402 SDK' - - - bash: | - export PATH=/c/Users/VssAdministrator/AppData/Local/Microsoft/dotnet:$PATH - dotnet --info - - displayName: 'Show installed sdk' - - - bash: | - export PATH=/c/Users/VssAdministrator/AppData/Local/Microsoft/dotnet:$PATH - cd build - curl -L https://github.com/filipw/dotnet-script/releases/download/0.28.0/dotnet-script.0.28.0.zip > dotnet-script.zip - unzip -o dotnet-script.zip -d ./ - displayName: 'Install dotnet-script' - - - bash: | - export PATH=/c/Users/VssAdministrator/AppData/Local/Microsoft/dotnet:$PATH - cd build - dotnet dotnet-script/dotnet-script.dll build.csx - displayName: 'Run build.csx' - env: - IS_SECURE_BUILDENVIRONMENT: $(IS_SECURE_BUILDENVIRONMENT) - GITHUB_REPO_TOKEN: $(GITHUB_REPO_TOKEN) - NUGET_APIKEY: $(NUGET_APIKEY) - CHOCOLATEY_APIKEY: $(CHOCOLATEY_APIKEY) - - - task: PublishPipelineArtifact@0 - displayName: 'Publish Pipeline Artifact' - inputs: - targetPath: build/Artifacts - diff --git a/build.sh b/build.sh index 8c3298d2..8af51e4a 100755 --- a/build.sh +++ b/build.sh @@ -3,9 +3,9 @@ SCRIPT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) DOTNET_SCRIPT="$SCRIPT_DIR/build/dotnet-script" if [ ! -d "$DOTNET_SCRIPT" ]; then - currentVersion=$(curl https://api.github.com/repos/filipw/dotnet-script/releases/latest?access_token=3a5de576bd32ddfccb52662d2d08d33a7edc318b | grep -Eo "\"tag_name\":\s*\"(.*)\"" | cut -d'"' -f4) + currentVersion=$(curl https://api.github.com/repos/dotnet-script/dotnet-script/releases/latest?access_token=3a5de576bd32ddfccb52662d2d08d33a7edc318b | grep -Eo "\"tag_name\":\s*\"(.*)\"" | cut -d'"' -f4) echo "Downloading dotnet-script version $currentVersion..." - curl -L https://github.com/filipw/dotnet-script/releases/download/$currentVersion/dotnet-script.$currentVersion.zip > "$SCRIPT_DIR/build/dotnet-script.zip" + curl -L https://github.com/dotnet-script/dotnet-script/releases/download/$currentVersion/dotnet-script.$currentVersion.zip > "$SCRIPT_DIR/build/dotnet-script.zip" unzip -o "$SCRIPT_DIR/build/dotnet-script.zip" -d "$SCRIPT_DIR/build/" if [ $? -ne 0 ]; then echo "An error occured while downloading dotnet-script" diff --git a/build/Build.csx b/build/Build.csx index b06778f7..3aa8a709 100644 --- a/build/Build.csx +++ b/build/Build.csx @@ -1,13 +1,12 @@ -#load "nuget:Dotnet.Build, 0.3.1" +#load "nuget:Dotnet.Build, 0.23.0" #load "nuget:dotnet-steps, 0.0.1" #load "nuget:github-changelog, 0.1.5" -#load "Choco.csx" #load "BuildContext.csx" -using static ReleaseManagement; +using System.Xml.Linq; using static ChangeLog; using static FileUtils; -using System.Xml.Linq; +using static ReleaseManagement; [StepDescription("Runs all tests.")] Step test = () => RunTests(); @@ -17,7 +16,6 @@ Step pack = () => { CreateGitHubReleaseAsset(); CreateNuGetPackages(); - CreateChocoPackage(); CreateGlobalToolPackage(); }; @@ -35,48 +33,37 @@ await StepRunner.Execute(Args); private void CreateGitHubReleaseAsset() { - DotNet.Publish(dotnetScriptProjectFolder, publishArtifactsFolder, "netcoreapp2.1"); + DotNet.Publish(dotnetScriptProjectFolder, publishArtifactsFolder, "net8.0"); Zip(publishArchiveFolder, pathToGitHubReleaseAsset); } - -private void CreateChocoPackage() -{ - if (BuildEnvironment.IsWindows) - { - Choco.Pack(dotnetScriptProjectFolder, publishArtifactsFolder, chocolateyArtifactsFolder); - } - else - { - Logger.Log("The choco package is only built on Windows"); - } -} - private void CreateGlobalToolPackage() { - using(var globalToolBuildFolder = new DisposableFolder()) - { - Copy(solutionFolder, globalToolBuildFolder.Path); - PatchPackAsTool(globalToolBuildFolder.Path); - PatchPackageId(globalToolBuildFolder.Path, GlobalToolPackageId); - PatchContent(globalToolBuildFolder.Path); - Command.Execute("dotnet",$"pack --configuration release --output {nuGetArtifactsFolder}", Path.Combine(globalToolBuildFolder.Path,"Dotnet.Script")); - } + using var globalToolBuildFolder = new DisposableFolder(); + Copy(solutionFolder, globalToolBuildFolder.Path); + PatchPackAsTool(globalToolBuildFolder.Path); + PatchPackageId(globalToolBuildFolder.Path, GlobalToolPackageId); + PatchContent(globalToolBuildFolder.Path); + Command.Execute("dotnet", $"pack --configuration release --output {nuGetArtifactsFolder}", Path.Combine(globalToolBuildFolder.Path, "Dotnet.Script")); } private void CreateNuGetPackages() { - Command.Execute("dotnet",$"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptProjectFolder); - Command.Execute("dotnet",$"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptCoreProjectFolder); - Command.Execute("dotnet",$"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptDependencyModelProjectFolder); - Command.Execute("dotnet",$"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptDependencyModelNuGetProjectFolder); + Command.Execute("dotnet", $"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptProjectFolder); + Command.Execute("dotnet", $"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptCoreProjectFolder); + Command.Execute("dotnet", $"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptDependencyModelProjectFolder); + Command.Execute("dotnet", $"pack --configuration release --output {nuGetArtifactsFolder}", dotnetScriptDependencyModelNuGetProjectFolder); } private void RunTests() { - DotNet.Test(testProjectFolder); - DotNet.Test(testDesktopProjectFolder); + Command.Execute("dotnet", "test -c Release -f net8.0", testProjectFolder); + Command.Execute("dotnet", "test -c Release -f net9.0", testProjectFolder); + if (BuildEnvironment.IsWindows) + { + DotNet.Test(testDesktopProjectFolder); + } } private async Task PublishRelease() @@ -97,11 +84,11 @@ private async Task PublishRelease() if (Git.Default.IsTagCommit()) { - Git.Default.RequreCleanWorkingTree(); - await ReleaseManagerFor(owner, projectName,BuildEnvironment.GitHubAccessToken) - .CreateRelease(Git.Default.GetLatestTag(), pathToReleaseNotes, new [] { new ZipReleaseAsset(pathToGitHubReleaseAsset) }); - NuGet.TryPush(nuGetArtifactsFolder); - Choco.TryPush(chocolateyArtifactsFolder, BuildEnvironment.ChocolateyApiKey); + Git.Default.RequireCleanWorkingTree(); + await ReleaseManagerFor(owner, projectName, BuildEnvironment.GitHubAccessToken) + .CreateRelease(Git.Default.GetLatestTag(), pathToReleaseNotes, new[] { new ZipReleaseAsset(pathToGitHubReleaseAsset) }); + + DotNet.TryPush(nuGetArtifactsFolder); } } @@ -118,7 +105,7 @@ private async Task CreateReleaseNotes() private void PatchPackAsTool(string solutionFolder) { - var pathToDotnetScriptProject = Path.Combine(solutionFolder,"Dotnet.Script","Dotnet.Script.csproj"); + var pathToDotnetScriptProject = Path.Combine(solutionFolder, "Dotnet.Script", "Dotnet.Script.csproj"); var projectFile = XDocument.Load(pathToDotnetScriptProject); var packAsToolElement = projectFile.Descendants("PackAsTool").Single(); packAsToolElement.Value = "true"; @@ -127,7 +114,7 @@ private void PatchPackAsTool(string solutionFolder) private void PatchPackageId(string solutionFolder, string packageId) { - var pathToDotnetScriptProject = Path.Combine(solutionFolder,"Dotnet.Script","Dotnet.Script.csproj"); + var pathToDotnetScriptProject = Path.Combine(solutionFolder, "Dotnet.Script", "Dotnet.Script.csproj"); var projectFile = XDocument.Load(pathToDotnetScriptProject); var packAsToolElement = projectFile.Descendants("PackageId").Single(); packAsToolElement.Value = packageId; @@ -136,7 +123,7 @@ private void PatchPackageId(string solutionFolder, string packageId) private void PatchContent(string solutionFolder) { - var pathToDotnetScriptProject = Path.Combine(solutionFolder,"Dotnet.Script","Dotnet.Script.csproj"); + var pathToDotnetScriptProject = Path.Combine(solutionFolder, "Dotnet.Script", "Dotnet.Script.csproj"); var projectFile = XDocument.Load(pathToDotnetScriptProject); var contentElements = projectFile.Descendants("Content").ToArray(); foreach (var contentElement in contentElements) diff --git a/build/BuildContext.csx b/build/BuildContext.csx index 36e5808a..1cfeeb07 100644 --- a/build/BuildContext.csx +++ b/build/BuildContext.csx @@ -1,4 +1,4 @@ -#load "nuget:Dotnet.Build, 0.3.1" +#load "nuget:Dotnet.Build, 0.23.0" using static FileUtils; using System.Xml.Linq; @@ -7,7 +7,7 @@ const string GlobalToolPackageId = "dotnet-script"; var owner = "filipw"; var projectName = "dotnet-script"; var root = FileUtils.GetScriptFolder(); -var solutionFolder = Path.Combine(root,"..","src"); +var solutionFolder = Path.Combine(root, "..", "src"); var dotnetScriptProjectFolder = Path.Combine(root, "..", "src", "Dotnet.Script"); var dotnetScriptCoreProjectFolder = Path.Combine(root, "..", "src", "Dotnet.Script.Core"); var dotnetScriptDependencyModelProjectFolder = Path.Combine(root, "..", "src", "Dotnet.Script.DependencyModel"); diff --git a/build/Choco.csx b/build/Choco.csx deleted file mode 100644 index 16de2eac..00000000 --- a/build/Choco.csx +++ /dev/null @@ -1,97 +0,0 @@ -#load "nuget:Dotnet.Build, 0.3.1" - -using System.Xml.Linq; - -public static class Choco -{ - /// - /// Creates a Chocolatey package based on a "csproj" project file. - /// - /// The path to the project folder. - /// The path to the output folder (*.nupkg) - public static void Pack(string pathToProjectFolder, string pathToBinaries, string outputFolder) - { - File.Copy(Path.Combine(pathToProjectFolder, "../../LICENSE"), Path.Combine("Chocolatey","tools","LICENSE.TXT"), true); - string pathToProjectFile = Directory.GetFiles(pathToProjectFolder, "*.csproj").Single(); - CreateSpecificationFromProject(pathToProjectFile, pathToBinaries); - Command.Execute("choco.exe", $@"pack Chocolatey\chocolatey.nuspec --outputdirectory {outputFolder}"); - } - - public static void Push(string packagesFolder, string apiKey, string source = "https://push.chocolatey.org/") - { - var packageFiles = Directory.GetFiles(packagesFolder, "*.nupkg"); - foreach(var packageFile in packageFiles) - { - Command.Execute("choco.exe", $"push {packageFile} --source {source} --key {apiKey}"); - } - } - - public static void TryPush(string packagesFolder, string apiKey, string source = "https://push.chocolatey.org/") - { - var packageFiles = Directory.GetFiles(packagesFolder, "*.nupkg"); - foreach(var packageFile in packageFiles) - { - Command.Execute("choco.exe", $"push {packageFile} --source {source} --key {apiKey}"); - } - } - - private static void CreateSpecificationFromProject(string pathToProjectFile, string pathToBinaries) - { - var projectFile = XDocument.Load(pathToProjectFile); - var authors = projectFile.Descendants("Authors").SingleOrDefault()?.Value; - var packageId = projectFile.Descendants("PackageId").SingleOrDefault()?.Value; - var description = projectFile.Descendants("Description").SingleOrDefault()?.Value; - var versionPrefix = projectFile.Descendants("VersionPrefix").SingleOrDefault()?.Value; - var versionSuffix = projectFile.Descendants("VersionSuffix").SingleOrDefault()?.Value; - - string version; - if (versionSuffix != null) - { - version = $"{versionPrefix}-{versionSuffix}"; - } - else - { - version = versionPrefix; - } - var tags = projectFile.Descendants("PackageTags").SingleOrDefault()?.Value; - var iconUrl = projectFile.Descendants("PackageIconUrl").SingleOrDefault()?.Value; - var projectUrl = projectFile.Descendants("PackageProjectUrl").SingleOrDefault()?.Value; - var repositoryUrl = projectFile.Descendants("RepositoryUrl").SingleOrDefault()?.Value; - - var packageElement = new XElement("package"); - var metadataElement = new XElement("metadata"); - packageElement.Add(metadataElement); - - // Package id should be lower case - // https://chocolatey.org/docs/create-packages#naming-your-package - metadataElement.Add(new XElement("id", packageId.ToLower())); - metadataElement.Add(new XElement("version", version)); - metadataElement.Add(new XElement("authors", authors)); - metadataElement.Add(new XElement("licenseUrl", "https://licenses.nuget.org/MIT")); - metadataElement.Add(new XElement("projectUrl", projectUrl)); - metadataElement.Add(new XElement("iconUrl", iconUrl)); - metadataElement.Add(new XElement("description", description)); - metadataElement.Add(new XElement("tags", repositoryUrl)); - - var filesElement = new XElement("files"); - packageElement.Add(filesElement); - - // Add the tools folder that contains "ChocolateyInstall.ps1" - filesElement.Add(CreateFileElement(@"tools\*.*",$@"{packageId}\tools")); - var srcGlobPattern = $@"{pathToBinaries}\**\*"; - filesElement.Add(CreateFileElement(srcGlobPattern,packageId)); - - using (var fileStream = new FileStream("Chocolatey/chocolatey.nuspec",FileMode.Create)) - { - new XDocument(packageElement).Save(fileStream); - } - } - - private static XElement CreateFileElement(string src, string target) - { - var srcAttribute = new XAttribute("src", src); - var targetAttribute = new XAttribute("target", target); - return new XElement("file", srcAttribute, targetAttribute); - } - -} \ No newline at end of file diff --git a/build/Chocolatey/tools/ChocolateyInstall.ps1 b/build/Chocolatey/tools/ChocolateyInstall.ps1 deleted file mode 100644 index 23b027f6..00000000 --- a/build/Chocolatey/tools/ChocolateyInstall.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -# Add the binary folder to the users PATH environment variable. -$pathToBinaries = Join-Path -Path $($env:ChocolateyPackageFolder) -ChildPath $($env:ChocolateyPackageName ) -Install-ChocolateyPath -PathToInstall "$pathToBinaries" -PathType 'Machine' \ No newline at end of file diff --git a/build/Chocolatey/tools/VERIFICATION.TXT b/build/Chocolatey/tools/VERIFICATION.TXT deleted file mode 100644 index 1936dea3..00000000 --- a/build/Chocolatey/tools/VERIFICATION.TXT +++ /dev/null @@ -1,8 +0,0 @@ -VERIFICATION -Verification is intended to assist the Chocolatey moderators and community -in verifying that this package's contents are trustworthy. - -dotnet-script.dll : 3BF42E83BE931AC98D02306DBC6FB319 - -Check against the corresponding file in this release. -https://github.com/filipw/dotnet-script/releases/download/0.13.0/dotnet-script.0.13.0.zip \ No newline at end of file diff --git a/build/Dockerfile b/build/Dockerfile index c77ad04f..876d8d79 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -1,24 +1,6 @@ -FROM microsoft/dotnet:2.1-sdk as builder -COPY . /dotnet-script -WORKDIR /dotnet-script +FROM mcr.microsoft.com/dotnet/sdk:7.0 -RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF -RUN apt update -RUN apt install apt-transport-https -RUN echo "deb https://download.mono-project.com/repo/debian stable-stretch main" | tee /etc/apt/sources.list.d/mono-official-stable.list +# https://www.nuget.org/packages/dotnet-script/ +RUN dotnet tool install dotnet-script --tool-path /usr/bin -RUN apt update -RUN apt install mono-devel -y -RUN apt install nuget -y - -RUN dotnet restore -RUN dotnet test src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj -RUN dotnet publish -c Release src/Dotnet.Script/Dotnet.Script.csproj -f netcoreapp2.1 - -FROM microsoft/dotnet:2.1-sdk - -COPY --from=builder /dotnet-script/src/Dotnet.Script/bin/Release/netcoreapp2.1/publish/ /dotnet-script/ - -WORKDIR /scripts - -ENTRYPOINT ["dotnet", "/dotnet-script/dotnet-script.dll"] +ENTRYPOINT [ "dotnet", "script" ] diff --git a/build/install-dotnet-script.ps1 b/build/install-dotnet-script.ps1 index 2e0def4c..2b155019 100644 --- a/build/install-dotnet-script.ps1 +++ b/build/install-dotnet-script.ps1 @@ -2,7 +2,7 @@ $scriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent $client = New-Object "System.Net.WebClient" -$url = "https://github.com/filipw/dotnet-script/releases/download/0.18.0/dotnet-script.0.18.0.zip" +$url = "https://github.com/dotnet-script/dotnet-script/releases/download/0.18.0/dotnet-script.0.18.0.zip" $file = "$scriptRoot/dotnet-script.zip" $client.DownloadFile($url,$file) Expand-Archive $file -DestinationPath $scriptRoot -Force \ No newline at end of file diff --git a/build/install-dotnet-script.sh b/build/install-dotnet-script.sh index 9242df21..4b3727c9 100755 --- a/build/install-dotnet-script.sh +++ b/build/install-dotnet-script.sh @@ -1,4 +1,4 @@ #!/bin/bash -curl -L https://github.com/filipw/dotnet-script/releases/download/0.18.0/dotnet-script.0.18.0.zip > dotnet-script.zip +curl -L https://github.com/dotnet-script/dotnet-script/releases/download/0.18.0/dotnet-script.0.18.0.zip > dotnet-script.zip unzip -o dotnet-script.zip -d ./ diff --git a/build/netfx.props b/build/netfx.props deleted file mode 100644 index 76d8ffc0..00000000 --- a/build/netfx.props +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - true - - - /Library/Frameworks/Mono.framework/Versions/Current/lib/mono - /usr/lib/mono - /usr/local/lib/mono - - - $(BaseFrameworkPathOverrideForMono)/4.5-api - $(BaseFrameworkPathOverrideForMono)/4.5.1-api - $(BaseFrameworkPathOverrideForMono)/4.5.2-api - $(BaseFrameworkPathOverrideForMono)/4.6-api - $(BaseFrameworkPathOverrideForMono)/4.6.1-api - $(BaseFrameworkPathOverrideForMono)/4.6.2-api - $(BaseFrameworkPathOverrideForMono)/4.7-api - $(BaseFrameworkPathOverrideForMono)/4.7.1-api - $(BaseFrameworkPathOverrideForMono)/4.7.2-api - true - - - $(FrameworkPathOverride)/Facades;$(AssemblySearchPaths) - - - - - - - - - - - \ No newline at end of file diff --git a/build/omnisharp.json b/build/omnisharp.json index 5c14541a..945a3c21 100644 --- a/build/omnisharp.json +++ b/build/omnisharp.json @@ -1,6 +1,6 @@ { "script": { "enableScriptNuGetReferences": true, - "defaultTargetFramework": "netcoreapp2.1" + "defaultTargetFramework": "net8.0" } } \ No newline at end of file diff --git a/build/prerestore.Dockerfile b/build/prerestore.Dockerfile new file mode 100644 index 00000000..48894424 --- /dev/null +++ b/build/prerestore.Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/dotnet/sdk:7.0 + +# https://www.nuget.org/packages/dotnet-script/ +RUN dotnet tool install dotnet-script --tool-path /usr/bin + +# Create a simple script, execute it and cleanup after +# to create the script project dir, which requires +# 'dotnet restore' to be run. +# This is necessary if you want to run this in a networkless +# docker container. +RUN dotnet script eval "Console.WriteLine(\"☑️ Prepared env for offline usage\")" + +ENTRYPOINT [ "dotnet", "script" ] diff --git a/global.json b/global.json new file mode 100644 index 00000000..97614a3d --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "9.0.100", + "rollForward": "latestFeature" + } +} diff --git a/install/install.ps1 b/install/install.ps1 index 2e8093ab..a0680936 100644 --- a/install/install.ps1 +++ b/install/install.ps1 @@ -5,7 +5,7 @@ $tempFolder = Join-Path $env:TEMP "dotnet-script" New-Item $tempFolder -ItemType Directory -Force # Get the latest release -$latestRelease = Invoke-WebRequest "https://api.github.com/repos/filipw/dotnet-script/releases/latest" | +$latestRelease = Invoke-WebRequest "https://api.github.com/repos/dotnet-script/dotnet-script/releases/latest" | ConvertFrom-Json | Select-Object tag_name $tag_name = $latestRelease.tag_name @@ -13,7 +13,7 @@ $tag_name = $latestRelease.tag_name # Download the zip Write-Host "Downloading latest version ($tag_name)" $client = New-Object "System.Net.WebClient" -$url = "https://github.com/filipw/dotnet-script/releases/download/$tag_name/dotnet-script.$tag_name.zip" +$url = "https://github.com/dotnet-script/dotnet-script/releases/download/$tag_name/dotnet-script.$tag_name.zip" $zipFile = Join-Path $tempFolder "dotnet-script.zip" $client.DownloadFile($url,$zipFile) diff --git a/install/install.sh b/install/install.sh index 3a464fc7..3cdf9ce5 100755 --- a/install/install.sh +++ b/install/install.sh @@ -1,7 +1,7 @@ #!/bin/bash mkdir /tmp/dotnet-script if [[ -z $1 ]]; then - version=$(curl https://api.github.com/repos/filipw/dotnet-script/releases/latest | grep -Eo "\"tag_name\":\s*\"(.*)\"" | cut -d'"' -f4) + version=$(curl https://api.github.com/repos/dotnet-script/dotnet-script/releases/latest | grep -Eo "\"tag_name\":\s*\"(.*)\"" | cut -d'"' -f4) else version=$1 fi @@ -15,7 +15,7 @@ if [[ $? -eq 0 ]]; then fi echo "Installing $version..." -curl -L https://github.com/filipw/dotnet-script/releases/download/$version/dotnet-script.$version.zip > /tmp/dotnet-script/dotnet-script.zip +curl -L https://github.com/dotnet-script/dotnet-script/releases/download/$version/dotnet-script.$version.zip > /tmp/dotnet-script/dotnet-script.zip unzip -o /tmp/dotnet-script/dotnet-script.zip -d /usr/local/lib chmod +x /usr/local/lib/dotnet-script/dotnet-script.sh cd /usr/local/bin diff --git a/omnisharp.json b/omnisharp.json new file mode 100644 index 00000000..973aa4c7 --- /dev/null +++ b/omnisharp.json @@ -0,0 +1,22 @@ +{ + "fileOptions": { + "systemExcludeSearchPatterns": [ + "**/TestFixtures/**/*.csx", + "**/ScriptPackages/**/*.csx" + ], + "userExcludeSearchPatterns": [] + }, + "script": { + "enableScriptNuGetReferences": true, + "defaultTargetFramework": "net6.0" + }, + "FormattingOptions": { + "organizeImports": true, + "enableEditorConfigSupport": true + }, + "RoslynExtensionsOptions": { + "enableImportCompletion": true, + "enableAnalyzersSupport": true, + "enableDecompilationSupport": true + } +} diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json index 6f31e44e..99987c10 100644 --- a/src/.vscode/settings.json +++ b/src/.vscode/settings.json @@ -1,3 +1,4 @@ { - "coverage-gutters.lcovname": "coverage.info" -} \ No newline at end of file + "coverage-gutters.lcovname": "coverage.info", + "dotnetCoreExplorer.searchpatterns": "**/bin/Debug/net5.0/Dotnet.Script.Tests.dll" +} diff --git a/src/.vscode/tasks.json b/src/.vscode/tasks.json index e4c713fe..d778ce8d 100644 --- a/src/.vscode/tasks.json +++ b/src/.vscode/tasks.json @@ -6,12 +6,34 @@ "command": "dotnet", "args": [ "build", - "${workspaceRoot}/Dotnet.Script/Dotnet.Script.csproj", "/property:GenerateFullPaths=true" ], - "group": { - "kind": "build", - "isDefault": true + "options": { + "cwd": "${workspaceFolder}/.." + }, + "type": "shell", + "group": "build", + "presentation": { + "reveal": "always" + }, + "problemMatcher": "$msCompile" + }, + { + "label": "rebuild", + "command": "dotnet", + "args": [ + "build", + "--no-incremental", + "/property:GenerateFullPaths=true" + ], + "options": { + "cwd": "${workspaceFolder}/.." + }, + "type": "shell", + "group": "build", + "presentation": { + "reveal": "always", + "clear": true }, "problemMatcher": "$msCompile" }, @@ -24,7 +46,7 @@ "-c", "release", "-f", - "netcoreapp2.1", + "net9.0", "${workspaceFolder}/Dotnet.Script.Tests/DotNet.Script.Tests.csproj" ], "problemMatcher": "$msCompile", @@ -42,7 +64,7 @@ "-c", "release", "-f", - "netcoreapp2.1", + "netcoreapp3.1", "/p:CollectCoverage=true", "/p:Exclude=\"[xunit*]*\"", "/p:CoverletOutputFormat=lcov", @@ -55,4 +77,4 @@ } } ] -} +} \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs index 688540b0..b9b6b039 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommand.cs @@ -22,15 +22,19 @@ public async Task Execute(ExecuteCodeCommandOptions options) { var sourceText = SourceText.From(options.Code); var context = new ScriptContext(sourceText, options.WorkingDirectory ?? Directory.GetCurrentDirectory(), options.Arguments, null, options.OptimizationLevel, ScriptMode.Eval, options.PackageSources); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); - var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; + var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; return await runner.Execute(context); } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs index b1bdf40b..11ebf3a7 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteCodeCommandOptions.cs @@ -1,4 +1,7 @@ using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -20,5 +23,14 @@ public ExecuteCodeCommandOptions(string code, string workingDirectory, string[] public OptimizationLevel OptimizationLevel { get; } public bool NoCache { get; } public string[] PackageSources { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs index 17d4f3cf..a883fce2 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommand.cs @@ -18,7 +18,12 @@ public ExecuteInteractiveCommand(ScriptConsole scriptConsole, LogFactory logFact public async Task Execute(ExecuteInteractiveCommandOptions options) { - var compiler = new ScriptCompiler(_logFactory, useRestoreCache: false); + var compiler = new ScriptCompiler(_logFactory, useRestoreCache: false) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var runner = new InteractiveRunner(compiler, _logFactory, _scriptConsole, options.PackageSources); if (options.ScriptFile == null) diff --git a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs index 5b189d06..bb7658ca 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteInteractiveCommandOptions.cs @@ -1,10 +1,12 @@ -using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { public class ExecuteInteractiveCommandOptions { - public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] arguments ,string[] packageSources) + public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] arguments, string[] packageSources) { ScriptFile = scriptFile; Arguments = arguments; @@ -14,5 +16,14 @@ public ExecuteInteractiveCommandOptions(ScriptFile scriptFile, string[] argument public ScriptFile ScriptFile { get; } public string[] Arguments { get; } public string[] PackageSources { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs index d7350e82..f2a9fd37 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommand.cs @@ -25,16 +25,20 @@ public async Task Execute(ExecuteLibraryCommandOptions options } var absoluteFilePath = options.LibraryPath.GetRootedPath(); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); - var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; + var runner = new ScriptRunner(compiler, _logFactory, _scriptConsole) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var result = await runner.Execute(absoluteFilePath, options.Arguments); return result; } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs index 2c0f4796..a3c3cb7e 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteLibraryCommandOptions.cs @@ -1,3 +1,7 @@ +#if NETCOREAPP +using System.Runtime.Loader; +#endif + namespace Dotnet.Script.Core.Commands { public class ExecuteLibraryCommandOptions @@ -12,5 +16,14 @@ public ExecuteLibraryCommandOptions(string libraryPath, string[] arguments, bool public string LibraryPath { get; } public string[] Arguments { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs index e62683dd..0712495a 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommand.cs @@ -31,8 +31,15 @@ public async Task Run(ExecuteScriptCommandOptions optio return await DownloadAndRunCode(options); } - var pathToLibrary = GetLibrary(options); - return await ExecuteLibrary(pathToLibrary, options.Arguments, options.NoCache); + var pathToLibrary = GetLibrary(options); + + var libraryOptions = new ExecuteLibraryCommandOptions(pathToLibrary, options.Arguments, options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; + return await new ExecuteLibraryCommand(_scriptConsole, _logFactory).Execute(libraryOptions); } private async Task DownloadAndRunCode(ExecuteScriptCommandOptions executeOptions) @@ -43,34 +50,32 @@ private async Task DownloadAndRunCode(ExecuteScriptCommandOpti return await new ExecuteCodeCommand(_scriptConsole, _logFactory).Execute(options); } - private string GetLibrary(ExecuteScriptCommandOptions executeOptions) + private string GetLibrary(ExecuteScriptCommandOptions executeOptions) { - var projectFolder = FileUtils.GetPathToTempFolder(Path.GetDirectoryName(executeOptions.File.Path)); + var projectFolder = FileUtils.GetPathToScriptTempFolder(executeOptions.File.Path); var executionCacheFolder = Path.Combine(projectFolder, "execution-cache"); var pathToLibrary = Path.Combine(executionCacheFolder, "script.dll"); - if (!TryCreateHash(executeOptions, out var hash) || !TryGetHash(executionCacheFolder, out var cachedHash)) + if (TryCreateHash(executeOptions, out var hash) + && TryGetHash(executionCacheFolder, out var cachedHash) + && string.Equals(hash, cachedHash)) { - return CreateLibrary(); + _logger.Debug($"Using cached compilation: " + pathToLibrary); + return pathToLibrary; } - if (!string.Equals(hash,cachedHash)) + var options = new PublishCommandOptions(executeOptions.File, executionCacheFolder, "script", PublishType.Library, executeOptions.OptimizationLevel, executeOptions.PackageSources, null, executeOptions.NoCache) { - return CreateLibrary(); - } - - return pathToLibrary; - - string CreateLibrary() +#if NETCOREAPP + AssemblyLoadContext = executeOptions.AssemblyLoadContext +#endif + }; + new PublishCommand(_scriptConsole, _logFactory).Execute(options); + if (hash != null) { - var options = new PublishCommandOptions(executeOptions.File,executionCacheFolder, "script", PublishType.Library,executeOptions.OptimizationLevel, executeOptions.PackageSources, null, executeOptions.NoCache); - new PublishCommand(_scriptConsole, _logFactory).Execute(options); - if (hash != null) - { - File.WriteAllText(Path.Combine(executionCacheFolder, "script.sha256"), hash); - } - return Path.Combine(executionCacheFolder, "script.dll"); + File.WriteAllText(Path.Combine(executionCacheFolder, "script.sha256"), hash); } + return Path.Combine(executionCacheFolder, "script.dll"); } public bool TryCreateHash(ExecuteScriptCommandOptions options, out string hash) @@ -103,6 +108,10 @@ public bool TryCreateHash(ExecuteScriptCommandOptions options, out string hash) var configuration = options.OptimizationLevel.ToString(); incrementalHash.AppendData(Encoding.UTF8.GetBytes(configuration)); + // Ensure that we don't run with the deps of an old target framework or SDK version. + incrementalHash.AppendData(Encoding.UTF8.GetBytes(ScriptEnvironment.Default.NetCoreVersion.Tfm)); + incrementalHash.AppendData(Encoding.UTF8.GetBytes(ScriptEnvironment.Default.NetCoreVersion.Version)); + hash = Convert.ToBase64String(incrementalHash.GetHashAndReset()); return true; } @@ -127,11 +136,5 @@ public bool TryGetHash(string cacheFolder, out string hash) hash = File.ReadAllText(pathToHashFile); return true; } - - private async Task ExecuteLibrary(string pathToLibrary, string[] arguments, bool noCache) - { - var options = new ExecuteLibraryCommandOptions(pathToLibrary, arguments, noCache); - return await new ExecuteLibraryCommand(_scriptConsole, _logFactory).Execute(options); - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs index 656cb74d..93577f01 100644 --- a/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/ExecuteScriptCommandOptions.cs @@ -1,4 +1,7 @@ using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -20,5 +23,14 @@ public ExecuteScriptCommandOptions(ScriptFile file, string[] arguments, Optimiza public string[] PackageSources { get; } public bool IsInteractive { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/InitCommand.cs b/src/Dotnet.Script.Core/Commands/InitCommand.cs index c286c439..cd8b0a1d 100644 --- a/src/Dotnet.Script.Core/Commands/InitCommand.cs +++ b/src/Dotnet.Script.Core/Commands/InitCommand.cs @@ -4,12 +4,10 @@ namespace Dotnet.Script.Core.Commands { public class InitCommand { - private readonly Logger _logger; private readonly LogFactory _logFactory; public InitCommand(LogFactory logFactory) { - _logger = logFactory.CreateLogger(); _logFactory = logFactory; } diff --git a/src/Dotnet.Script.Core/Commands/PublishCommand.cs b/src/Dotnet.Script.Core/Commands/PublishCommand.cs index 33b0525a..08a09ffb 100644 --- a/src/Dotnet.Script.Core/Commands/PublishCommand.cs +++ b/src/Dotnet.Script.Core/Commands/PublishCommand.cs @@ -10,16 +10,19 @@ public class PublishCommand { private readonly ScriptConsole _scriptConsole; private readonly LogFactory _logFactory; - private readonly Logger _logger; public PublishCommand(ScriptConsole scriptConsole, LogFactory logFactory) { _scriptConsole = scriptConsole; _logFactory = logFactory; - _logger = logFactory.CreateLogger(); } public void Execute(PublishCommandOptions options) + { + Execute(options); + } + + public void Execute(PublishCommandOptions options) { var absoluteFilePath = options.File.Path; @@ -30,7 +33,12 @@ public void Execute(PublishCommandOptions options) (options.PublishType == PublishType.Library ? Path.Combine(Path.GetDirectoryName(absoluteFilePath), "publish") : Path.Combine(Path.GetDirectoryName(absoluteFilePath), "publish", options.RuntimeIdentifier)); var absolutePublishDirectory = publishDirectory.GetRootedPath(); - var compiler = GetScriptCompiler(!options.NoCache, _logFactory); + var compiler = new ScriptCompiler(_logFactory, !options.NoCache) + { +#if NETCOREAPP + AssemblyLoadContext = options.AssemblyLoadContext +#endif + }; var scriptEmitter = new ScriptEmitter(_scriptConsole, compiler); var publisher = new ScriptPublisher(_logFactory, scriptEmitter); var code = absoluteFilePath.ToSourceText(); @@ -38,19 +46,12 @@ public void Execute(PublishCommandOptions options) if (options.PublishType == PublishType.Library) { - publisher.CreateAssembly(context, _logFactory, options.LibraryName); + publisher.CreateAssembly(context, _logFactory, options.LibraryName); } else { - publisher.CreateExecutable(context, _logFactory, options.RuntimeIdentifier); + publisher.CreateExecutable(context, _logFactory, options.RuntimeIdentifier, options.LibraryName); } } - - private static ScriptCompiler GetScriptCompiler(bool useRestoreCache, LogFactory logFactory) - { - - var compiler = new ScriptCompiler(logFactory, useRestoreCache); - return compiler; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs b/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs index a82fa295..7dd44e1c 100644 --- a/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs +++ b/src/Dotnet.Script.Core/Commands/PublishCommandOptions.cs @@ -1,5 +1,8 @@ using Dotnet.Script.DependencyModel.Environment; using Microsoft.CodeAnalysis; +#if NETCOREAPP +using System.Runtime.Loader; +#endif namespace Dotnet.Script.Core.Commands { @@ -25,6 +28,15 @@ public PublishCommandOptions(ScriptFile file, string outputDirectory, string lib public string[] PackageSources { get; } public string RuntimeIdentifier { get; } public bool NoCache { get; } + +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script isolation. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif } public enum PublishType diff --git a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj index 294cdded..94dd6e45 100644 --- a/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj +++ b/src/Dotnet.Script.Core/Dotnet.Script.Core.csproj @@ -1,54 +1,41 @@ - - + + A cross platform library allowing you to run C# (CSX) scripts with support for debugging and inline NuGet packages. Based on Roslyn. - 0.29.0 + 1.6.0 filipw - netstandard2.0 + net9.0;net8.0;netstandard2.0 Dotnet.Script.Core Dotnet.Script.Core script;csx;csharp;roslyn - https://raw.githubusercontent.com/filipw/Strathweb.TypedRouting.AspNetCore/master/strathweb.png - https://github.com/filipw/dotnet-script + https://avatars.githubusercontent.com/u/113979420 + https://github.com/dotnet-script/dotnet-script MIT git - https://github.com/filipw/dotnet-script.git + https://github.com/dotnet-script/dotnet-script.git false false false + 9.0 + true + ../dotnet-script.snk - - - - - + - - - - - - - - - - + + + - - + + + + - - - - - - + \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Extensions.cs b/src/Dotnet.Script.Core/Extensions.cs index dbe06315..83c10fc7 100644 --- a/src/Dotnet.Script.Core/Extensions.cs +++ b/src/Dotnet.Script.Core/Extensions.cs @@ -9,10 +9,8 @@ public static class Extensions public static SourceText ToSourceText(this string absoluteFilePath) { - using (var filestream = File.OpenRead(absoluteFilePath)) - { - return SourceText.From(filestream); - } + using var filestream = File.OpenRead(absoluteFilePath); + return SourceText.From(filestream); } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Interactive/InteractiveCommandProvider.cs b/src/Dotnet.Script.Core/Interactive/InteractiveCommandProvider.cs index 7065efc8..faa443b3 100644 --- a/src/Dotnet.Script.Core/Interactive/InteractiveCommandProvider.cs +++ b/src/Dotnet.Script.Core/Interactive/InteractiveCommandProvider.cs @@ -4,7 +4,7 @@ namespace Dotnet.Script.Core { public class InteractiveCommandProvider { - private IInteractiveCommand[] _commands = new IInteractiveCommand[] + private readonly IInteractiveCommand[] _commands = new IInteractiveCommand[] { new ResetInteractiveCommand(), new ClsCommand(), diff --git a/src/Dotnet.Script.Core/Interactive/InteractiveRunner.cs b/src/Dotnet.Script.Core/Interactive/InteractiveRunner.cs index 507ad79b..4ffcecf4 100644 --- a/src/Dotnet.Script.Core/Interactive/InteractiveRunner.cs +++ b/src/Dotnet.Script.Core/Interactive/InteractiveRunner.cs @@ -21,13 +21,12 @@ public class InteractiveRunner private bool _shouldExit = false; private ScriptState _scriptState; private ScriptOptions _scriptOptions; - private InteractiveScriptGlobals _globals; - + private readonly InteractiveScriptGlobals _globals; protected Logger Logger; protected ScriptCompiler ScriptCompiler; protected ScriptConsole Console; private readonly string[] _packageSources; - protected CSharpParseOptions ParseOptions = new CSharpParseOptions(LanguageVersion.Latest, kind: SourceCodeKind.Script); + protected CSharpParseOptions ParseOptions = new CSharpParseOptions(LanguageVersion.Preview, kind: SourceCodeKind.Script); protected InteractiveCommandProvider InteractiveCommandParser = new InteractiveCommandProvider(); protected string CurrentDirectory = Directory.GetCurrentDirectory(); @@ -113,9 +112,18 @@ public virtual void Exit() private async Task RunFirstScript(ScriptContext scriptContext) { foreach (var arg in scriptContext.Args) + { _globals.Args.Add(arg); + } var compilationContext = ScriptCompiler.CreateCompilationContext(scriptContext); + Console.WriteDiagnostics(compilationContext.Warnings, compilationContext.Errors); + + if (compilationContext.Errors.Any()) + { + throw new CompilationErrorException("Script compilation failed due to one or more errors.", compilationContext.Errors.ToImmutableArray()); + } + _scriptState = await compilationContext.Script.RunAsync(_globals, ex => true).ConfigureAwait(false); _scriptOptions = compilationContext.ScriptOptions; } diff --git a/src/Dotnet.Script.Core/Internal/PreprocessorLineRewriter.cs b/src/Dotnet.Script.Core/Internal/PreprocessorLineRewriter.cs index 84ef8cdf..e2d88866 100644 --- a/src/Dotnet.Script.Core/Internal/PreprocessorLineRewriter.cs +++ b/src/Dotnet.Script.Core/Internal/PreprocessorLineRewriter.cs @@ -22,13 +22,13 @@ public override SyntaxNode VisitReferenceDirectiveTrivia(ReferenceDirectiveTrivi return HandleSkippedTrivia(base.VisitReferenceDirectiveTrivia(node)); } - private SyntaxNode HandleSkippedTrivia(SyntaxNode node) + private static SyntaxNode HandleSkippedTrivia(SyntaxNode node) { var skippedTrivia = node.DescendantTrivia().Where(x => x.RawKind == (int)SyntaxKind.SkippedTokensTrivia).FirstOrDefault(); - if (skippedTrivia != null && skippedTrivia.Token.Kind() != SyntaxKind.None) + if (!skippedTrivia.Token.IsKind(SyntaxKind.None)) { var firstToken = skippedTrivia.GetStructure().ChildTokens().FirstOrDefault(); - if (firstToken != null && firstToken.Kind() == SyntaxKind.BadToken && firstToken.ToFullString().Trim() == ";") + if (firstToken.IsKind(SyntaxKind.BadToken) && firstToken.ToFullString().Trim() == ";") { node = node.ReplaceToken(firstToken, SyntaxFactory.Token(SyntaxKind.None)); skippedTrivia = node.DescendantTrivia().Where(x => x.RawKind == (int)SyntaxKind.SkippedTokensTrivia).FirstOrDefault(); diff --git a/src/Dotnet.Script.Core/Properties/AssemblyInfo.cs b/src/Dotnet.Script.Core/Properties/AssemblyInfo.cs index 704593a0..232e7d89 100644 --- a/src/Dotnet.Script.Core/Properties/AssemblyInfo.cs +++ b/src/Dotnet.Script.Core/Properties/AssemblyInfo.cs @@ -17,3 +17,5 @@ // The following GUID is for the ID of the typelib if this project is exposed to COM [assembly: Guid("684fefee-451b-4e68-b662-c16e3a7da794")] +[assembly: InternalsVisibleTo("Dotnet.Script.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9790935628475aa44c2b2025427926d965f49fb36c645f5aeb7c03bd6183d31d02f42add3b2cb3ec4b0e508c5cf02478b41008ac8a6a6db314eaca3d678caac419d95e6e85e6cfb16fd93fec0bed8bc707875126e577ee7c96efe15737679d1a4dd1affec9e8f5c1b6f6518d51bcd1d3718b8b2694853eef059328ca81f6ea9")] +[assembly: InternalsVisibleTo("Dotnet.Script.Shared.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9790935628475aa44c2b2025427926d965f49fb36c645f5aeb7c03bd6183d31d02f42add3b2cb3ec4b0e508c5cf02478b41008ac8a6a6db314eaca3d678caac419d95e6e85e6cfb16fd93fec0bed8bc707875126e577ee7c96efe15737679d1a4dd1affec9e8f5c1b6f6518d51bcd1d3718b8b2694853eef059328ca81f6ea9")] diff --git a/src/Dotnet.Script.Core/Scaffolder.cs b/src/Dotnet.Script.Core/Scaffolder.cs index c9181bf9..8e52acc1 100644 --- a/src/Dotnet.Script.Core/Scaffolder.cs +++ b/src/Dotnet.Script.Core/Scaffolder.cs @@ -2,27 +2,32 @@ using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Process; -using Newtonsoft.Json.Linq; using System; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; +using System.Text.Json.Nodes; using System.Text.RegularExpressions; namespace Dotnet.Script.Core { public class Scaffolder { - private ScriptEnvironment _scriptEnvironment; + private readonly ScriptEnvironment _scriptEnvironment; private const string DefaultScriptFileName = "main.csx"; - private ScriptConsole _scriptConsole = ScriptConsole.Default; - private CommandRunner _commandRunner; + private readonly ScriptConsole _scriptConsole; + private readonly CommandRunner _commandRunner; - public Scaffolder(LogFactory logFactory) + public Scaffolder(LogFactory logFactory) : this(logFactory, ScriptConsole.Default, ScriptEnvironment.Default) + { + } + + public Scaffolder(LogFactory logFactory, ScriptConsole scriptConsole, ScriptEnvironment scriptEnvironment) { _commandRunner = new CommandRunner(logFactory); - _scriptEnvironment = ScriptEnvironment.Default; + _scriptConsole = scriptConsole; + _scriptEnvironment = scriptEnvironment; } public void InitializerFolder(string fileName, string currentWorkingDirectory) @@ -58,7 +63,7 @@ public void CreateNewScriptFile(string fileName, string currentDirectory) RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { // mark .csx file as executable, this activates the shebang to run dotnet-script as interpreter - _commandRunner.Execute($"/bin/chmod", $"+x {pathToScriptFile}"); + _commandRunner.Execute($"/bin/chmod", $"+x \"{pathToScriptFile}\""); } _scriptConsole.WriteSuccess($"...'{pathToScriptFile}' [Created]"); } @@ -75,7 +80,8 @@ public void RegisterFileHandler() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { - // register dotnet-script as the tool to process .csx files + // register dotnet-script as the tool to process .csx files + _commandRunner.Execute("reg", @"delete HKCU\Software\classes\.csx /f"); _commandRunner.Execute("reg", @"add HKCU\Software\classes\.csx /f /ve /t REG_SZ /d dotnetscript"); _commandRunner.Execute("reg", $@"add HKCU\Software\Classes\dotnetscript\Shell\Open\Command /f /ve /t REG_EXPAND_SZ /d ""\""%ProgramFiles%\dotnet\dotnet.exe\"" script \""%1\"" -- %*"""); } @@ -114,7 +120,7 @@ private void CreateOmniSharpConfigurationFile(string currentWorkingDirectory) if (!File.Exists(pathToOmniSharpJson)) { var omniSharpFileTemplate = TemplateLoader.ReadTemplate("omnisharp.json.template"); - JObject settings = JObject.Parse(omniSharpFileTemplate); + var settings = JsonObject.Parse(omniSharpFileTemplate); settings["script"]["defaultTargetFramework"] = _scriptEnvironment.TargetFramework; File.WriteAllText(pathToOmniSharpJson, settings.ToString()); _scriptConsole.WriteSuccess($"...'{pathToOmniSharpJson}' [Created]"); @@ -136,28 +142,51 @@ private void CreateLaunchConfiguration(string currentWorkingDirectory) _scriptConsole.WriteNormal("Creating VS Code launch configuration file"); string pathToLaunchFile = Path.Combine(vsCodeDirectory, "launch.json"); string installLocation = _scriptEnvironment.InstallLocation; + bool isInstalledAsGlobalTool = installLocation.Contains($".dotnet{Path.DirectorySeparatorChar}tools", StringComparison.OrdinalIgnoreCase); string dotnetScriptPath = Path.Combine(installLocation, "dotnet-script.dll").Replace(@"\", "/"); + string launchFileContent; if (!File.Exists(pathToLaunchFile)) { - string launchFileTemplate = TemplateLoader.ReadTemplate("launch.json.template"); - string launchFileContent = launchFileTemplate.Replace("PATH_TO_DOTNET-SCRIPT", dotnetScriptPath); + if (isInstalledAsGlobalTool) + { + launchFileContent = TemplateLoader.ReadTemplate("globaltool.launch.json.template"); + } + else + { + string launchFileTemplate = TemplateLoader.ReadTemplate("launch.json.template"); + launchFileContent = launchFileTemplate.Replace("PATH_TO_DOTNET-SCRIPT", dotnetScriptPath); + } + File.WriteAllText(pathToLaunchFile, launchFileContent); _scriptConsole.WriteSuccess($"...'{pathToLaunchFile}' [Created]"); } else { _scriptConsole.WriteHighlighted($"...'{pathToLaunchFile}' already exists' [Skipping]"); - var launchFileContent = File.ReadAllText(pathToLaunchFile); - string pattern = @"^(\s*"")(.*dotnet-script.dll)("").*$"; - if (Regex.IsMatch(launchFileContent, pattern, RegexOptions.Multiline)) + launchFileContent = File.ReadAllText(pathToLaunchFile); + if (isInstalledAsGlobalTool) + { + var template = TemplateLoader.ReadTemplate("globaltool.launch.json.template"); + if (template != launchFileContent) + { + File.WriteAllText(pathToLaunchFile, template); + _scriptConsole.WriteHighlighted("...Use global tool launch config [Updated]"); + } + } + else { - var newLaunchFileContent = Regex.Replace(launchFileContent, pattern, $"$1{dotnetScriptPath}$3", RegexOptions.Multiline); - if (launchFileContent != newLaunchFileContent) + string pattern = @"^(\s*"")(.*dotnet-script.dll)(""\s*,).*$"; + if (Regex.IsMatch(launchFileContent, pattern, RegexOptions.Multiline)) { - _scriptConsole.WriteHighlighted($"...Fixed path to dotnet-script: '{dotnetScriptPath}' [Updated]"); - File.WriteAllText(pathToLaunchFile, newLaunchFileContent); + var newLaunchFileContent = Regex.Replace(launchFileContent, pattern, $"$1{dotnetScriptPath}$3", RegexOptions.Multiline); + if (launchFileContent != newLaunchFileContent) + { + _scriptConsole.WriteHighlighted($"...Fixed path to dotnet-script: '{dotnetScriptPath}' [Updated]"); + File.WriteAllText(pathToLaunchFile, newLaunchFileContent); + } } } + } } } diff --git a/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs new file mode 100644 index 00000000..603661ae --- /dev/null +++ b/src/Dotnet.Script.Core/ScriptAssemblyLoadContext.cs @@ -0,0 +1,170 @@ +#if NET + +using System; +using System.Reflection; +using System.Runtime.Loader; + +#nullable enable + +namespace Dotnet.Script.Core +{ + /// + /// Represents assembly load context for a script with full and automatic assembly isolation. + /// + public class ScriptAssemblyLoadContext : AssemblyLoadContext + { + /// + /// Initializes a new instance of the class. + /// + public ScriptAssemblyLoadContext() + { + } + +#if NET5_0_OR_GREATER + /// + /// Initializes a new instance of the class + /// with a name and a value that indicates whether unloading is enabled. + /// + /// + /// + public ScriptAssemblyLoadContext(string? name, bool isCollectible = false) : + base(name, isCollectible) + { + } + + /// + /// Initializes a new instance of the class + /// with a value that indicates whether unloading is enabled. + /// + /// + protected ScriptAssemblyLoadContext(bool isCollectible) : + base(isCollectible) + { + } +#endif + + /// + /// + /// Gets the value indicating whether a specified assembly is homogeneous. + /// + /// + /// Homogeneous assemblies are those shared by both host and scripts. + /// + /// + /// The assembly name. + /// true if the specified assembly is homogeneous; otherwise, false. + protected internal virtual bool IsHomogeneousAssembly(AssemblyName assemblyName) + { + var name = assemblyName.Name; + return + string.Equals(name, "mscorlib", StringComparison.OrdinalIgnoreCase) || + string.Equals(name, "Microsoft.CodeAnalysis.Scripting", StringComparison.OrdinalIgnoreCase); + } + + /// + protected override Assembly? Load(AssemblyName assemblyName) => InvokeLoading(assemblyName); + + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) => InvokeLoadingUnmanagedDll(unmanagedDllName); + + /// + /// Provides data for the event. + /// + internal sealed class LoadingEventArgs : EventArgs + { + public LoadingEventArgs(AssemblyName assemblyName) + { + Name = assemblyName; + } + + public AssemblyName Name { get; } + } + + /// + /// Represents a method that handles the event. + /// + /// The sender. + /// The arguments. + /// The loaded assembly or null if the assembly cannot be resolved. + internal delegate Assembly? LoadingEventHandler(ScriptAssemblyLoadContext sender, LoadingEventArgs args); + + LoadingEventHandler? m_Loading; + + /// + /// Occurs when an assembly is being loaded. + /// + internal event LoadingEventHandler Loading + { + add => m_Loading += value; + remove => m_Loading -= value; + } + + Assembly? InvokeLoading(AssemblyName assemblyName) + { + var eh = m_Loading; + if (eh != null) + { + var args = new LoadingEventArgs(assemblyName); + foreach (LoadingEventHandler handler in eh.GetInvocationList()) + { + var assembly = handler(this, args); + if (assembly != null) + return assembly; + } + } + return null; + } + + /// + /// Provides data for the event. + /// + internal sealed class LoadingUnmanagedDllEventArgs : EventArgs + { + public LoadingUnmanagedDllEventArgs(string unmanagedDllName, Func loadUnmanagedDllFromPath) + { + UnmanagedDllName = unmanagedDllName; + LoadUnmanagedDllFromPath = loadUnmanagedDllFromPath; + } + + public string UnmanagedDllName { get; } + public Func LoadUnmanagedDllFromPath { get; } + } + + /// + /// Represents a method that handles the event. + /// + /// The sender. + /// The arguments. + /// The loaded DLL or if the DLL cannot be resolved. + internal delegate IntPtr LoadingUnmanagedDllEventHandler(ScriptAssemblyLoadContext sender, LoadingUnmanagedDllEventArgs args); + + LoadingUnmanagedDllEventHandler? m_LoadingUnmanagedDll; + + /// + /// Occurs when an unmanaged DLL is being loaded. + /// + internal event LoadingUnmanagedDllEventHandler LoadingUnmanagedDll + { + add => m_LoadingUnmanagedDll += value; + remove => m_LoadingUnmanagedDll -= value; + } + + IntPtr InvokeLoadingUnmanagedDll(string unmanagedDllName) + { + var eh = m_LoadingUnmanagedDll; + if (eh != null) + { + var args = new LoadingUnmanagedDllEventArgs(unmanagedDllName, LoadUnmanagedDllFromPath); + foreach (LoadingUnmanagedDllEventHandler handler in eh.GetInvocationList()) + { + var dll = handler(this, args); + if (dll != IntPtr.Zero) + return dll; + } + } + return IntPtr.Zero; + } + } +} + +#endif diff --git a/src/Dotnet.Script.Core/ScriptCompilationContext.cs b/src/Dotnet.Script.Core/ScriptCompilationContext.cs index 71040b64..4c77dac3 100644 --- a/src/Dotnet.Script.Core/ScriptCompilationContext.cs +++ b/src/Dotnet.Script.Core/ScriptCompilationContext.cs @@ -1,7 +1,9 @@ using Dotnet.Script.DependencyModel.Runtime; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; using Microsoft.CodeAnalysis.Text; +using System.Linq; namespace Dotnet.Script.Core { @@ -15,15 +17,22 @@ public class ScriptCompilationContext public ScriptOptions ScriptOptions { get; } - public RuntimeDependency[] RuntimeDependencies {get;} + public RuntimeDependency[] RuntimeDependencies { get; } - public ScriptCompilationContext(Script script, SourceText sourceText, InteractiveAssemblyLoader loader, ScriptOptions scriptOptions, RuntimeDependency[] runtimeDependencies) + public Diagnostic[] Warnings { get; } + + public Diagnostic[] Errors { get; } + + public ScriptCompilationContext(Script script, SourceText sourceText, InteractiveAssemblyLoader loader, ScriptOptions scriptOptions, RuntimeDependency[] runtimeDependencies, Diagnostic[] diagnostics) { Script = script; SourceText = sourceText; ScriptOptions = scriptOptions; Loader = loader; RuntimeDependencies = runtimeDependencies; + + Warnings = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Warning).ToArray(); + Errors = diagnostics.Where(x => x.Severity == DiagnosticSeverity.Error).ToArray(); } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/ScriptCompiler.cs b/src/Dotnet.Script.Core/ScriptCompiler.cs index d908337d..2cfdab5e 100644 --- a/src/Dotnet.Script.Core/ScriptCompiler.cs +++ b/src/Dotnet.Script.Core/ScriptCompiler.cs @@ -3,6 +3,9 @@ using System.Collections.Immutable; using System.Linq; using System.Reflection; +#if NETCOREAPP +using System.Runtime.Loader; +#endif using System.Text; using System.Threading.Tasks; using Dotnet.Script.Core.Internal; @@ -23,18 +26,12 @@ namespace Dotnet.Script.Core { public class ScriptCompiler { - private ScriptEnvironment _scriptEnvironment; + private readonly ScriptEnvironment _scriptEnvironment; - private Logger _logger; + private readonly Logger _logger; static ScriptCompiler() { - // reset default scripting mode to latest language version to enable C# 8.0 features - // this is not needed once Roslyn 3.0 stable ships - var csharpScriptCompilerType = typeof(CSharpScript).GetTypeInfo().Assembly.GetType("Microsoft.CodeAnalysis.CSharp.Scripting.CSharpScriptCompiler"); - var parseOptionsField = csharpScriptCompilerType?.GetField("s_defaultOptions", BindingFlags.Static | BindingFlags.NonPublic); - parseOptionsField?.SetValue(null, new CSharpParseOptions(LanguageVersion.CSharp8, kind: SourceCodeKind.Script)); - // force Roslyn to use ReferenceManager for the first time Task.Run(() => { @@ -42,6 +39,7 @@ static ScriptCompiler() }); } + //Note: We should set this according to the SDK being used protected virtual IEnumerable ImportedNamespaces => new[] { "System", @@ -57,14 +55,16 @@ static ScriptCompiler() }; // see: https://github.com/dotnet/roslyn/issues/5501 - protected virtual IEnumerable SuppressedDiagnosticIds => new[] { "CS1701", "CS1702", "CS1705" }; + protected virtual IEnumerable SuppressedDiagnosticIds { get; } = new[] { "CS1701", "CS1702", "CS1705" }; - public CSharpParseOptions ParseOptions { get; } = new CSharpParseOptions(LanguageVersion.Latest, kind: SourceCodeKind.Script); + protected virtual Dictionary SpecificDiagnosticOptions { get; } = new Dictionary(); + + public CSharpParseOptions ParseOptions { get; } = new CSharpParseOptions(LanguageVersion.Preview, kind: SourceCodeKind.Script); public RuntimeDependencyResolver RuntimeDependencyResolver { get; } public ScriptCompiler(LogFactory logFactory, bool useRestoreCache) - :this(logFactory, new RuntimeDependencyResolver(logFactory, useRestoreCache)) + : this(logFactory, new RuntimeDependencyResolver(logFactory, useRestoreCache)) { } @@ -74,16 +74,32 @@ private ScriptCompiler(LogFactory logFactory, RuntimeDependencyResolver runtimeD _logger = logFactory(typeof(ScriptCompiler)); _scriptEnvironment = ScriptEnvironment.Default; RuntimeDependencyResolver = runtimeDependencyResolver; + + // nullable diagnostic options should be set to errors + for (var i = 8600; i <= 8655; i++) + { + SpecificDiagnosticOptions.Add($"CS{i}", ReportDiagnostic.Error); + } } +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif + public virtual ScriptOptions CreateScriptOptions(ScriptContext context, IList runtimeDependencies) { var scriptMap = runtimeDependencies.ToDictionary(rdt => rdt.Name, rdt => rdt.Scripts); var opts = ScriptOptions.Default.AddImports(ImportedNamespaces) - .WithSourceResolver(new NuGetSourceReferenceResolver(new SourceFileResolver(ImmutableArray.Empty, context.WorkingDirectory),scriptMap)) + .WithSourceResolver(new NuGetSourceReferenceResolver(new SourceFileResolver(ImmutableArray.Empty, context.WorkingDirectory), scriptMap)) .WithMetadataResolver(new NuGetMetadataReferenceResolver(ScriptMetadataResolver.Default.WithBaseDirectory(context.WorkingDirectory))) .WithEmitDebugInformation(true) - .WithFileEncoding(context.Code.Encoding); + .WithLanguageVersion(LanguageVersion.Preview) + .WithFileEncoding(context.Code.Encoding ?? Encoding.UTF8); // if the framework is not Core CLR, add GAC references if (!ScriptEnvironment.Default.IsNetCore) @@ -98,6 +114,16 @@ public virtual ScriptOptions CreateScriptOptions(ScriptContext context, IList CreateCompilationContext CreateCompilationContext(code, scriptOptions, typeof(THost), loader); - SetOptimizationLevel(context, script); + SetCompilationOptions(context, script); + + var orderedDiagnostics = script.GetDiagnostics(); + var suppressedDiagnostics = orderedDiagnostics.Where(d => SuppressedDiagnosticIds.Contains(d.Id)); + foreach (var suppressedDiagnostic in suppressedDiagnostics) + { + _logger.Debug($"Suppressed diagnostic {suppressedDiagnostic.Id}: {suppressedDiagnostic.ToString()}"); + } - EvaluateDiagnostics(script); + var nonSuppressedDiagnostics = orderedDiagnostics.Except(suppressedDiagnostics).ToArray(); - return new ScriptCompilationContext(script, context.Code, loader, scriptOptions, runtimeDependencies); + return new ScriptCompilationContext(script, context.Code, loader, scriptOptions, runtimeDependencies, nonSuppressedDiagnostics); } private RuntimeDependency[] GetRuntimeDependencies(ScriptContext context) @@ -157,7 +189,19 @@ private ScriptOptions AddScriptReferences(ScriptOptions scriptOptions, Dictionar { foreach (var runtimeAssembly in scriptDependenciesMap.Values) { - loadedAssembliesMap.TryGetValue(runtimeAssembly.Name.Name, out var loadedAssembly); + bool homogenization; +#if NETCOREAPP + homogenization = + AssemblyLoadContext is not ScriptAssemblyLoadContext salc || + salc.IsHomogeneousAssembly(runtimeAssembly.Name); +#else + homogenization = true; +#endif + + Assembly loadedAssembly = null; + if (homogenization) + loadedAssembliesMap.TryGetValue(runtimeAssembly.Name.Name, out loadedAssembly); + if (loadedAssembly == null) { _logger.Trace("Adding reference to a runtime dependency => " + runtimeAssembly); @@ -174,29 +218,18 @@ private ScriptOptions AddScriptReferences(ScriptOptions scriptOptions, Dictionar return scriptOptions; } - private void EvaluateDiagnostics(Script script) + private void SetCompilationOptions(ScriptContext context, Script script) { - var orderedDiagnostics = script.GetDiagnostics(); - - var suppressedDiagnostics = orderedDiagnostics.Where(d => SuppressedDiagnosticIds.Contains(d.Id)); - foreach (var suppressedDiagnostic in suppressedDiagnostics) - { - _logger.Debug($"Suppressed diagnostic {suppressedDiagnostic.Id}: {suppressedDiagnostic.ToString()}"); - } - - if (orderedDiagnostics.Except(suppressedDiagnostics).Any(d => d.Severity == DiagnosticSeverity.Error)) - { - throw new CompilationErrorException("Script compilation failed due to one or more errors.", - orderedDiagnostics.ToImmutableArray()); - } - } + var compilationOptionsField = typeof(CSharpCompilation).GetTypeInfo().GetDeclaredField("_options"); + var compilation = script.GetCompilation(); + var compilationOptions = (CSharpCompilationOptions)compilationOptionsField.GetValue(compilation); + compilationOptions = compilationOptions.WithSpecificDiagnosticOptions(SpecificDiagnosticOptions.ToImmutableDictionary()); + compilationOptionsField.SetValue(compilation, compilationOptions); - private void SetOptimizationLevel(ScriptContext context, Script script) - { if (context.OptimizationLevel == OptimizationLevel.Release) { _logger.Debug("Configuration/Optimization mode: Release"); - SetReleaseOptimizationLevel(script.GetCompilation()); + SetReleaseOptimizationLevel(compilation); } else { @@ -258,7 +291,7 @@ private Assembly MapUnresolvedAssemblyToRuntimeLibrary(IDictionary assemblyName.Version) { loadedAssemblyMap.TryGetValue(assemblyName.Name, out var loadedAssembly); - if(loadedAssembly != null) + if (loadedAssembly != null) { _logger.Trace($"Redirecting {assemblyName} to already loaded {loadedAssembly.GetName().Name}"); return loadedAssembly; diff --git a/src/Dotnet.Script.Core/ScriptConsole.cs b/src/Dotnet.Script.Core/ScriptConsole.cs index 14d4daab..6f46bf6d 100644 --- a/src/Dotnet.Script.Core/ScriptConsole.cs +++ b/src/Dotnet.Script.Core/ScriptConsole.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Microsoft.CodeAnalysis; using RL = System.ReadLine; namespace Dotnet.Script.Core @@ -35,11 +36,37 @@ public virtual void WriteHighlighted(string value) Console.ResetColor(); } + public virtual void WriteWarning(string value) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Error.WriteLine(value.TrimEnd(Environment.NewLine.ToCharArray())); + Console.ResetColor(); + } + public virtual void WriteNormal(string value) { Out.WriteLine(value.TrimEnd(Environment.NewLine.ToCharArray())); } + public virtual void WriteDiagnostics(Diagnostic[] warningDiagnostics, Diagnostic[] errorDiagnostics) + { + if (warningDiagnostics != null) + { + foreach (var warning in warningDiagnostics) + { + WriteWarning(warning.ToString()); + } + } + + if (errorDiagnostics != null) + { + foreach (var error in errorDiagnostics) + { + WriteError(error.ToString()); + } + } + } + public virtual string ReadLine() { return In == null ? RL.Read() : In.ReadLine(); diff --git a/src/Dotnet.Script.Core/ScriptDownloader.cs b/src/Dotnet.Script.Core/ScriptDownloader.cs old mode 100644 new mode 100755 index 0bf5fc02..f0c62d98 --- a/src/Dotnet.Script.Core/ScriptDownloader.cs +++ b/src/Dotnet.Script.Core/ScriptDownloader.cs @@ -1,7 +1,8 @@ using System; using System.IO; +using System.IO.Compression; +using System.Net; using System.Net.Http; -using System.Net.Mime; using System.Threading.Tasks; namespace Dotnet.Script.Core @@ -10,25 +11,31 @@ public class ScriptDownloader { public async Task Download(string uri) { - const string plainTextMediaType = "text/plain"; - using (HttpClient client = new HttpClient()) + using HttpClient client = new HttpClient(new HttpClientHandler { - using (HttpResponseMessage response = await client.GetAsync(uri)) - { - response.EnsureSuccessStatusCode(); + // Avoid Deflate due to bugs. For more info, see: + // https://github.com/weblinq/WebLinq/issues/132 + AutomaticDecompression = DecompressionMethods.GZip + }); + using HttpResponseMessage response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead); + response.EnsureSuccessStatusCode(); - using (HttpContent content = response.Content) - { - string mediaType = content.Headers.ContentType.MediaType; - - if (string.IsNullOrWhiteSpace(mediaType) || mediaType.Equals(plainTextMediaType, StringComparison.InvariantCultureIgnoreCase)) - { - return await content.ReadAsStringAsync(); - } - - throw new NotSupportedException($"The media type '{mediaType}' is not supported when executing a script over http/https"); - } - } + using HttpContent content = response.Content; + var mediaType = content.Headers.ContentType?.MediaType?.ToLowerInvariant().Trim(); + switch (mediaType) + { + case null: + case "": + case "text/plain": + return await content.ReadAsStringAsync(); + case "application/gzip": + case "application/x-gzip": + using (var stream = await content.ReadAsStreamAsync()) + using (var gzip = new GZipStream(stream, CompressionMode.Decompress)) + using (var reader = new StreamReader(gzip)) + return await reader.ReadToEndAsync(); + default: + throw new NotSupportedException($"The media type '{mediaType}' is not supported when executing a script over http/https"); } } } diff --git a/src/Dotnet.Script.Core/ScriptEmitter.cs b/src/Dotnet.Script.Core/ScriptEmitter.cs index 7f854d2c..c0e10e16 100644 --- a/src/Dotnet.Script.Core/ScriptEmitter.cs +++ b/src/Dotnet.Script.Core/ScriptEmitter.cs @@ -1,7 +1,9 @@ using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; +using System.Collections.Immutable; using System.IO; +using System.Linq; namespace Dotnet.Script.Core { @@ -16,40 +18,46 @@ public ScriptEmitter(ScriptConsole scriptConsole, ScriptCompiler scriptCompiler) _scriptCompiler = scriptCompiler; } - public virtual ScriptEmitResult Emit(ScriptContext context) + public virtual ScriptEmitResult Emit(ScriptContext context, string assemblyName) { - try + var compilationContext = _scriptCompiler.CreateCompilationContext(context); + foreach (var warning in compilationContext.Warnings) { - var compilationContext = _scriptCompiler.CreateCompilationContext(context); - - var compilation = compilationContext.Script.GetCompilation(); + _scriptConsole.WriteWarning(warning.ToString()); + } - var peStream = new MemoryStream(); - EmitOptions emitOptions = null; - if (context.OptimizationLevel == Microsoft.CodeAnalysis.OptimizationLevel.Debug) + if (compilationContext.Errors.Any()) + { + foreach (var diagnostic in compilationContext.Errors) { - emitOptions = new EmitOptions() - .WithDebugInformationFormat(DebugInformationFormat.Embedded); + _scriptConsole.WriteError(diagnostic.ToString()); } - var result = compilation.Emit(peStream, options: emitOptions); + throw new CompilationErrorException("Script compilation failed due to one or more errors.", compilationContext.Errors.ToImmutableArray()); + } - if (result.Success) - { - return new ScriptEmitResult(peStream, compilation.DirectiveReferences, compilationContext.RuntimeDependencies); - } + var compilation = compilationContext.Script.GetCompilation(); + compilation = compilation.WithAssemblyName(assemblyName); - return ScriptEmitResult.Error(result.Diagnostics); - } - catch (CompilationErrorException e) + var peStream = new MemoryStream(); + EmitOptions emitOptions = null; + + if (context.OptimizationLevel == Microsoft.CodeAnalysis.OptimizationLevel.Debug) { - foreach (var diagnostic in e.Diagnostics) - { - _scriptConsole.WriteError(diagnostic.ToString()); - } + emitOptions = new EmitOptions() + .WithDebugInformationFormat(DebugInformationFormat.Embedded); + - throw; } + + var result = compilation.Emit(peStream, options: emitOptions); + + if (result.Success) + { + return new ScriptEmitResult(peStream, compilation.DirectiveReferences, compilationContext.RuntimeDependencies); + } + + return ScriptEmitResult.Error(result.Diagnostics); } } } diff --git a/src/Dotnet.Script.Core/ScriptPublisher.cs b/src/Dotnet.Script.Core/ScriptPublisher.cs index ca2f7ae4..faaf6b05 100644 --- a/src/Dotnet.Script.Core/ScriptPublisher.cs +++ b/src/Dotnet.Script.Core/ScriptPublisher.cs @@ -1,10 +1,8 @@ -using Dotnet.Script.Core.Internal; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Process; using Dotnet.Script.DependencyModel.ProjectSystem; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Scripting; using System; using System.IO; using System.Reflection; @@ -13,7 +11,7 @@ namespace Dotnet.Script.Core { public class ScriptPublisher { - private const string ScriptingVersion = "2.8.2"; + private const string ScriptingVersion = "4.11.0"; private readonly ScriptProjectProvider _scriptProjectProvider; private readonly ScriptEmitter _scriptEmitter; @@ -45,7 +43,7 @@ public void CreateAssembly(ScriptContext context, LogFactory log assemblyFileName = assemblyFileName ?? Path.GetFileNameWithoutExtension(context.FilePath); var scriptAssemblyPath = CreateScriptAssembly(context, context.WorkingDirectory, assemblyFileName); - var tempProjectPath = ScriptProjectProvider.GetPathToProjectFile(Path.GetDirectoryName(context.FilePath)); + var tempProjectPath = ScriptProjectProvider.GetPathToProjectFile(Path.GetDirectoryName(context.FilePath), ScriptEnvironment.Default.TargetFramework); var tempProjectDirecory = Path.GetDirectoryName(tempProjectPath); var sourceProjectAssetsPath = Path.Combine(tempProjectDirecory, "obj", "project.assets.json"); @@ -57,35 +55,42 @@ public void CreateAssembly(ScriptContext context, LogFactory log File.Copy(sourceNugetPropsPath, destinationNugetPropsPath, overwrite: true); // only display published if we aren't auto publishing to temp folder - if (!scriptAssemblyPath.StartsWith(Path.GetTempPath())) + if (!scriptAssemblyPath.StartsWith(FileUtils.GetTempPath())) { - _scriptConsole.WriteSuccess($"Published {context.FilePath} to { scriptAssemblyPath}"); + _scriptConsole.WriteSuccess($"Published {context.FilePath} to {scriptAssemblyPath}"); } } - public void CreateExecutable(ScriptContext context, LogFactory logFactory, string runtimeIdentifier) + public void CreateExecutable(ScriptContext context, LogFactory logFactory, string runtimeIdentifier, string executableFileName = null) { if (runtimeIdentifier == null) { throw new ArgumentNullException(nameof(runtimeIdentifier)); } + executableFileName ??= Path.GetFileNameWithoutExtension(context.FilePath); const string AssemblyName = "scriptAssembly"; - var tempProjectPath = ScriptProjectProvider.GetPathToProjectFile(Path.GetDirectoryName(context.FilePath)); + var tempProjectPath = ScriptProjectProvider.GetPathToProjectFile(Path.GetDirectoryName(context.FilePath), _scriptEnvironment.TargetFramework); + var renamedProjectPath = ScriptProjectProvider.GetPathToProjectFile(Path.GetDirectoryName(context.FilePath), _scriptEnvironment.TargetFramework, executableFileName); var tempProjectDirectory = Path.GetDirectoryName(tempProjectPath); var scriptAssemblyPath = CreateScriptAssembly(context, tempProjectDirectory, AssemblyName); + var projectFile = new ProjectFile(File.ReadAllText(tempProjectPath)); projectFile.PackageReferences.Add(new PackageReference("Microsoft.CodeAnalysis.Scripting", ScriptingVersion)); projectFile.AssemblyReferences.Add(new AssemblyReference(scriptAssemblyPath)); - projectFile.Save(tempProjectPath); + projectFile.Save(renamedProjectPath); CopyProgramTemplate(tempProjectDirectory); var commandRunner = new CommandRunner(logFactory); // todo: may want to add ability to return dotnet.exe errors - var exitcode = commandRunner.Execute("dotnet", $"publish \"{tempProjectPath}\" -c Release -r {runtimeIdentifier} -o {context.WorkingDirectory}"); + var publishSingleFileArgument = ScriptEnvironment.Default.NetCoreVersion.Major >= 3 ? "/p:PublishSingleFile=true" : string.Empty; + var includeNativeLibrariesForSelfExtract = ScriptEnvironment.Default.NetCoreVersion.Major >= 5 ? "/p:IncludeNativeLibrariesForSelfExtract=true" : string.Empty; + + var exitcode = commandRunner.Execute("dotnet", $"publish \"{renamedProjectPath}\" -c Release -r {runtimeIdentifier} -o \"{context.WorkingDirectory}\" {publishSingleFileArgument} {includeNativeLibrariesForSelfExtract} /p:DebugType=Embedded"); + if (exitcode != 0) { throw new Exception($"dotnet publish failed with result '{exitcode}'"); @@ -96,79 +101,62 @@ public void CreateExecutable(ScriptContext context, LogFactory l private string CreateScriptAssembly(ScriptContext context, string outputDirectory, string assemblyFileName) { - try + var emitResult = _scriptEmitter.Emit(context, assemblyFileName); + var assemblyPath = Path.Combine(outputDirectory, $"{assemblyFileName}.dll"); + using (var peFileStream = new FileStream(assemblyPath, FileMode.Create)) + using (emitResult.PeStream) + { + emitResult.PeStream.WriteTo(peFileStream); + } + + foreach (var reference in emitResult.DirectiveReferences) { - var emitResult = _scriptEmitter.Emit(context); - if (!emitResult.Success) + if (reference.Display.EndsWith(".NuGet.dll")) { - throw new CompilationErrorException("One or more errors occurred when emitting the assembly", emitResult.Diagnostics); + continue; } - var assemblyPath = Path.Combine(outputDirectory, $"{assemblyFileName}.dll"); - using (var peFileStream = new FileStream(assemblyPath, FileMode.Create)) - using (emitResult.PeStream) + var referenceFileInfo = new FileInfo(reference.Display); + var fullPathToReference = Path.GetFullPath(referenceFileInfo.FullName); + var fullPathToNewAssembly = Path.GetFullPath(Path.Combine(outputDirectory, referenceFileInfo.Name)); + + if (!Equals(fullPathToReference, fullPathToNewAssembly)) { - emitResult.PeStream.WriteTo(peFileStream); + File.Copy(fullPathToReference, fullPathToNewAssembly, true); } + } - foreach (var reference in emitResult.DirectiveReferences) + /* The following is needed to make native assets work. + During a regular "dotnet publish" we find these assets in a "runtimes" folder. + We must copy these binaries up to the same folder as the script library so + that they can be found during execution. + */ + foreach (var runtimeDependency in emitResult.RuntimeDependencies) + { + if (!runtimeDependency.Name.Contains("microsoft.netcore", StringComparison.OrdinalIgnoreCase)) { - if (reference.Display.EndsWith(".NuGet.dll")) - { - continue; - } - - var referenceFileInfo = new FileInfo(reference.Display); - var fullPathToReference = Path.GetFullPath(referenceFileInfo.FullName); - var fullPathToNewAssembly = Path.GetFullPath(Path.Combine(outputDirectory, referenceFileInfo.Name)); - - if (!Equals(fullPathToReference, fullPathToNewAssembly)) + foreach (var nativeAsset in runtimeDependency.NativeAssets) { - File.Copy(fullPathToReference, fullPathToNewAssembly, true); + File.Copy(nativeAsset, Path.Combine(outputDirectory, Path.GetFileName(nativeAsset)), true); } - } - - /* The following is needed to make native assets work. - During a regular "dotnet publish" we find these assets in a "runtimes" folder. - We must copy these binaries up to the same folder as the script library so - that they can be found during execution. - */ - foreach (var runtimeDependency in emitResult.RuntimeDependencies) - { - if (!runtimeDependency.Name.Contains("microsoft.netcore", StringComparison.OrdinalIgnoreCase)) + foreach (var runtimeAssembly in runtimeDependency.Assemblies) { - foreach (var nativeAsset in runtimeDependency.NativeAssets) - { - File.Copy(nativeAsset, Path.Combine(outputDirectory, Path.GetFileName(nativeAsset)), true); - } - foreach (var runtimeAssembly in runtimeDependency.Assemblies) + File.Copy(runtimeAssembly.Path, Path.Combine(outputDirectory, Path.GetFileName(runtimeAssembly.Path)), true); + var pathToRuntimeAssemblyFolder = Path.GetDirectoryName(runtimeAssembly.Path); + var pdbFileName = $"{Path.GetFileNameWithoutExtension(runtimeAssembly.Path)}.pdb"; + var pathToPdb = Path.Combine(pathToRuntimeAssemblyFolder, pdbFileName); + if (File.Exists(pathToPdb)) { - File.Copy(runtimeAssembly.Path, Path.Combine(outputDirectory, Path.GetFileName(runtimeAssembly.Path)), true); - var pathToRuntimeAssemblyFolder = Path.GetDirectoryName(runtimeAssembly.Path); - var pdbFileName = $"{Path.GetFileNameWithoutExtension(runtimeAssembly.Path)}.pdb"; - var pathToPdb = Path.Combine(pathToRuntimeAssemblyFolder, pdbFileName); - if (File.Exists(pathToPdb)) - { - File.Copy(pathToPdb, Path.Combine(outputDirectory, Path.GetFileName(pathToPdb)), true); - } + File.Copy(pathToPdb, Path.Combine(outputDirectory, Path.GetFileName(pathToPdb)), true); } } } - - return assemblyPath; - } - catch (CompilationErrorException ex) - { - _scriptConsole.WriteError(ex.Message); - foreach (var diagnostic in ex.Diagnostics) - { - _scriptConsole.WriteError(diagnostic.ToString()); - } - throw; } + + return assemblyPath; } - private void CopyProgramTemplate(string tempProjectDirecory) + private static void CopyProgramTemplate(string tempProjectDirecory) { const string resourceName = "Dotnet.Script.Core.Templates.program.publish.template"; diff --git a/src/Dotnet.Script.Core/ScriptRunner.cs b/src/Dotnet.Script.Core/ScriptRunner.cs index ef6c96dd..66b605fe 100644 --- a/src/Dotnet.Script.Core/ScriptRunner.cs +++ b/src/Dotnet.Script.Core/ScriptRunner.cs @@ -1,10 +1,16 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using System.Reflection; +#if NETCOREAPP +using System.Runtime.Loader; +#endif using System.Threading.Tasks; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Runtime; +using Gapotchenko.FX.Reflection; using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; using Microsoft.CodeAnalysis.Scripting; using Microsoft.CodeAnalysis.Scripting.Hosting; @@ -26,44 +32,100 @@ public ScriptRunner(ScriptCompiler scriptCompiler, LogFactory logFactory, Script _scriptEnvironment = ScriptEnvironment.Default; } +#if NETCOREAPP +#nullable enable + /// + /// Gets or sets a custom assembly load context to use for script execution. + /// + public AssemblyLoadContext? AssemblyLoadContext { get; init; } +#nullable restore +#endif + public async Task Execute(string dllPath, IEnumerable commandLineArgs) { +#if NETCOREAPP + var assemblyLoadContext = AssemblyLoadContext; + var assemblyLoadPal = assemblyLoadContext != null ? new AssemblyLoadPal(assemblyLoadContext) : AssemblyLoadPal.ForCurrentAppDomain; +#else + var assemblyLoadPal = AssemblyLoadPal.ForCurrentAppDomain; +#endif + var runtimeDeps = ScriptCompiler.RuntimeDependencyResolver.GetDependenciesForLibrary(dllPath); var runtimeDepsMap = ScriptCompiler.CreateScriptDependenciesMap(runtimeDeps); - var assembly = Assembly.LoadFrom(dllPath); // this needs to be called prior to 'AppDomain.CurrentDomain.AssemblyResolve' event handler added + var assembly = assemblyLoadPal.LoadFrom(dllPath); // this needs to be called prior to 'AssemblyLoadPal.Resolving' event handler added + +#if NETCOREAPP + using var assemblyAutoLoader = assemblyLoadContext != null ? new AssemblyAutoLoader(assemblyLoadContext) : null; + assemblyAutoLoader?.AddAssembly(assembly); - AppDomain.CurrentDomain.AssemblyResolve += (sender, args) => + Assembly OnLoading(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingEventArgs args) { - var assemblyName = new AssemblyName(args.Name); - var result = runtimeDepsMap.TryGetValue(assemblyName.Name, out RuntimeAssembly runtimeAssembly); - if (!result) throw new Exception($"Unable to locate assembly '{assemblyName.Name}: {assemblyName.Version}'"); - var loadedAssembly = Assembly.LoadFrom(runtimeAssembly.Path); - return loadedAssembly; - }; + var assemblyName = args.Name; - var type = assembly.GetType("Submission#0"); - var method = type.GetMethod("", BindingFlags.Static | BindingFlags.Public); + if (sender.IsHomogeneousAssembly(assemblyName)) + { + // The default assembly loader will take care of it. + return null; + } - var globals = new CommandLineScriptGlobals(ScriptConsole.Out, CSharpObjectFormatter.Instance); - foreach (var arg in commandLineArgs) - globals.Args.Add(arg); + return ResolveAssembly(assemblyLoadPal, assemblyName, runtimeDepsMap); + } - var submissionStates = new object[2]; - submissionStates[0] = globals; + IntPtr OnLoadingUnmanagedDll(ScriptAssemblyLoadContext sender, ScriptAssemblyLoadContext.LoadingUnmanagedDllEventArgs args) + { + string dllPath = assemblyAutoLoader.ResolveUnmanagedDllPath(args.UnmanagedDllName); + if (dllPath == null) + return IntPtr.Zero; + return args.LoadUnmanagedDllFromPath(dllPath); + } + + var scriptAssemblyLoadContext = assemblyLoadContext as ScriptAssemblyLoadContext; + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.Loading += OnLoading; + scriptAssemblyLoadContext.LoadingUnmanagedDll += OnLoadingUnmanagedDll; + } - var resultTask = method.Invoke(null, new[] { submissionStates }) as Task; - TReturn returnValue; + using var contextualReflectionScope = assemblyLoadContext != null ? assemblyLoadContext.EnterContextualReflection() : default; +#endif + + Assembly OnResolving(AssemblyLoadPal sender, AssemblyLoadPal.ResolvingEventArgs args) => ResolveAssembly(sender, args.Name, runtimeDepsMap); + + assemblyLoadPal.Resolving += OnResolving; try { - returnValue = await resultTask; + var type = assembly.GetType("Submission#0"); + var method = type.GetMethod("", BindingFlags.Static | BindingFlags.Public); + + var globals = new CommandLineScriptGlobals(ScriptConsole.Out, CSharpObjectFormatter.Instance); + foreach (var arg in commandLineArgs) + globals.Args.Add(arg); + + var submissionStates = new object[2]; + submissionStates[0] = globals; + + try + { + var resultTask = (Task)method.Invoke(null, new[] { submissionStates }); + return await resultTask; + } + catch (System.Exception ex) + { + ScriptConsole.WriteError(ex.ToString()); + throw new ScriptRuntimeException("Script execution resulted in an exception.", ex); + } } - catch (System.Exception ex) + finally { - ScriptConsole.WriteError(ex.ToString()); - throw new ScriptRuntimeException("Script execution resulted in an exception.", ex); + assemblyLoadPal.Resolving -= OnResolving; +#if NETCOREAPP + if (scriptAssemblyLoadContext != null) + { + scriptAssemblyLoadContext.LoadingUnmanagedDll -= OnLoadingUnmanagedDll; + scriptAssemblyLoadContext.Loading -= OnLoading; + } +#endif } - - return await resultTask; } public Task Execute(ScriptContext context) @@ -78,20 +140,15 @@ public Task Execute(ScriptContext context) public virtual Task Execute(ScriptContext context, THost host) { - try + var compilationContext = ScriptCompiler.CreateCompilationContext(context); + ScriptConsole.WriteDiagnostics(compilationContext.Warnings, compilationContext.Errors); + + if (compilationContext.Errors.Any()) { - var compilationContext = ScriptCompiler.CreateCompilationContext(context); - return Execute(compilationContext, host); + throw new CompilationErrorException("Script compilation failed due to one or more errors.", compilationContext.Errors.ToImmutableArray()); } - catch (CompilationErrorException e) - { - foreach (var diagnostic in e.Diagnostics) - { - ScriptConsole.WriteError(diagnostic.ToString()); - } - throw; - } + return Execute(compilationContext, host); } public virtual async Task Execute(ScriptCompilationContext compilationContext, THost host) @@ -100,6 +157,14 @@ public virtual async Task Execute(ScriptCompilationCont return ProcessScriptState(scriptResult); } + internal Assembly ResolveAssembly(AssemblyLoadPal pal, AssemblyName assemblyName, Dictionary runtimeDepsMap) + { + var result = runtimeDepsMap.TryGetValue(assemblyName.Name, out RuntimeAssembly runtimeAssembly); + if (!result) return null; + var loadedAssembly = pal.LoadFrom(runtimeAssembly.Path); + return loadedAssembly; + } + protected TReturn ProcessScriptState(ScriptState scriptState) { if (scriptState.Exception != null) diff --git a/src/Dotnet.Script.Core/Templates/TemplateLoader.cs b/src/Dotnet.Script.Core/Templates/TemplateLoader.cs index 593f932c..e4bee252 100644 --- a/src/Dotnet.Script.Core/Templates/TemplateLoader.cs +++ b/src/Dotnet.Script.Core/Templates/TemplateLoader.cs @@ -8,10 +8,8 @@ public static class TemplateLoader public static string ReadTemplate(string name) { var resourceStream = typeof(TemplateLoader).GetTypeInfo().Assembly.GetManifestResourceStream($"Dotnet.Script.Core.Templates.{name}"); - using (var streamReader = new StreamReader(resourceStream)) - { - return streamReader.ReadToEnd(); - } + using var streamReader = new StreamReader(resourceStream); + return streamReader.ReadToEnd(); } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Templates/globaltool.launch.json.template b/src/Dotnet.Script.Core/Templates/globaltool.launch.json.template new file mode 100644 index 00000000..305245ba --- /dev/null +++ b/src/Dotnet.Script.Core/Templates/globaltool.launch.json.template @@ -0,0 +1,17 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Script Debug", + "type": "coreclr", + "request": "launch", + "program": "${env:HOME}/.dotnet/tools/dotnet-script", + "args": ["${file}"], + "windows": { + "program": "${env:USERPROFILE}/.dotnet/tools/dotnet-script.exe", + }, + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + } + ] +} \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Templates/launch.json.template b/src/Dotnet.Script.Core/Templates/launch.json.template index 24492703..9b4b1c3b 100644 --- a/src/Dotnet.Script.Core/Templates/launch.json.template +++ b/src/Dotnet.Script.Core/Templates/launch.json.template @@ -12,7 +12,7 @@ "${file}" ], "cwd": "${workspaceRoot}", - "stopAtEntry": true + "stopAtEntry": false } ] } \ No newline at end of file diff --git a/src/Dotnet.Script.Core/Templates/program.publish.template b/src/Dotnet.Script.Core/Templates/program.publish.template index ba79887a..2b269482 100644 --- a/src/Dotnet.Script.Core/Templates/program.publish.template +++ b/src/Dotnet.Script.Core/Templates/program.publish.template @@ -1,6 +1,7 @@ using System; using Microsoft.CodeAnalysis.CSharp.Scripting.Hosting; using Microsoft.CodeAnalysis.Scripting.Hosting; +using System.Runtime.Loader; using System.Threading.Tasks; using static System.Console; using System.Reflection; @@ -18,15 +19,14 @@ namespace dotnetPublishCode foreach (var arg in args) globals.Args.Add(arg); - var path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - var assembly = Assembly.LoadFrom(Path.Combine(path, "scriptAssembly.dll")); + var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName("scriptAssembly")); var type = assembly.GetType("Submission#0"); var factoryMethod = type.GetMethod(""); if (factoryMethod == null) throw new Exception("couldn't find factory method to initiate script"); var invokeTask = factoryMethod.Invoke(null, new object[] { new object[] { globals, null } }) as Task; var invokeResult = await invokeTask; - if (invokeResult != 0) + if (invokeResult != 0) { WritePrettyError($"Error result: '{invokeResult}'"); return 0x1; diff --git a/src/Dotnet.Script.Core/Versioning/EnvironmentReporter.cs b/src/Dotnet.Script.Core/Versioning/EnvironmentReporter.cs index b641a727..3155d5f9 100644 --- a/src/Dotnet.Script.Core/Versioning/EnvironmentReporter.cs +++ b/src/Dotnet.Script.Core/Versioning/EnvironmentReporter.cs @@ -78,11 +78,11 @@ private void ReportThatNewVersionIsAvailable(VersionInfo latestVersion) if (ScriptEnvironment.Default.IsWindows) { updateInfo.AppendLine("Chocolatey : choco upgrade Dotnet.Script"); - updateInfo.AppendLine("Powershell : (new-object Net.WebClient).DownloadString(\"https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.ps\") | iex"); + updateInfo.AppendLine("Powershell : (new-object Net.WebClient).DownloadString(\"https://raw.githubusercontent.com/dotnet-script/dotnet-script/master/install/install.ps\") | iex"); } else { - updateInfo.AppendLine("Bash : curl -s https://raw.githubusercontent.com/filipw/dotnet-script/master/install/install.sh | bash"); + updateInfo.AppendLine("Bash : curl -s https://raw.githubusercontent.com/dotnet-script/dotnet-script/master/install/install.sh | bash"); } _scriptConsole.WriteHighlighted(updateInfo.ToString()); diff --git a/src/Dotnet.Script.Core/Versioning/LoggedVersionProvider.cs b/src/Dotnet.Script.Core/Versioning/LoggedVersionProvider.cs index 8bd8a66e..2d90a54b 100644 --- a/src/Dotnet.Script.Core/Versioning/LoggedVersionProvider.cs +++ b/src/Dotnet.Script.Core/Versioning/LoggedVersionProvider.cs @@ -11,7 +11,7 @@ namespace Dotnet.Script.Core.Versioning public class LoggedVersionProvider : IVersionProvider { private readonly IVersionProvider _versionProvider; - private Logger _logger; + private readonly Logger _logger; /// /// Initializes a new instance of the class. diff --git a/src/Dotnet.Script.Core/Versioning/VersionProvider.cs b/src/Dotnet.Script.Core/Versioning/VersionProvider.cs index 11107a5e..b9f9acc6 100644 --- a/src/Dotnet.Script.Core/Versioning/VersionProvider.cs +++ b/src/Dotnet.Script.Core/Versioning/VersionProvider.cs @@ -2,9 +2,8 @@ using System.Linq; using System.Net.Http; using System.Reflection; +using System.Text.Json.Nodes; using System.Threading.Tasks; -using Dotnet.Script.DependencyModel.Logging; -using Newtonsoft.Json.Linq; namespace Dotnet.Script.Core.Versioning { @@ -14,12 +13,12 @@ namespace Dotnet.Script.Core.Versioning public class VersionProvider : IVersionProvider { private const string UserAgent = "dotnet-script"; - private static readonly string RequestUri = "/repos/filipw/dotnet-script/releases/latest"; + private static readonly string RequestUri = "/repos/dotnet-script/dotnet-script/releases/latest"; - /// + /// public async Task GetLatestVersion() { - using(var httpClient = CreateHttpClient()) + using (var httpClient = CreateHttpClient()) { var response = await httpClient.GetStringAsync(RequestUri); return ParseTagName(response); @@ -34,8 +33,8 @@ HttpClient CreateHttpClient() VersionInfo ParseTagName(string json) { - JObject jsonResult = JObject.Parse(json); - return new VersionInfo(jsonResult.SelectToken("tag_name").Value(), isResolved:true); + JsonNode jsonResult = JsonNode.Parse(json); + return new VersionInfo(jsonResult["tag_name"].GetValue(), isResolved: true); } } @@ -43,7 +42,7 @@ VersionInfo ParseTagName(string json) public VersionInfo GetCurrentVersion() { var versionAttribute = typeof(VersionProvider).Assembly.GetCustomAttributes().Single(); - return new VersionInfo(versionAttribute.InformationalVersion, isResolved:true); + return new VersionInfo(versionAttribute.InformationalVersion, isResolved: true); } } } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj b/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj index fc960f75..c2794916 100644 --- a/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj +++ b/src/Dotnet.Script.DependencyModel.Nuget/Dotnet.Script.DependencyModel.NuGet.csproj @@ -1,19 +1,25 @@ - - + + netstandard2.0 MIT - https://github.com/filipw/dotnet-script - https://raw.githubusercontent.com/filipw/Strathweb.TypedRouting.AspNetCore/master/strathweb.png - https://github.com/filipw/dotnet-script.git + https://github.com/dotnet-script/dotnet-script + https://avatars.githubusercontent.com/u/113979420 + https://github.com/dotnet-script/dotnet-script.git git script;csx;csharp;roslyn;nuget - 0.8.0 + 1.6.0 A MetadataReferenceResolver that allows inline nuget references to be specified in script(csx) files. dotnet-script dotnet-script + latest + true + ../dotnet-script.snk - - - + + + + + + \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel.Nuget/NuGetMetadataReferenceResolver.cs b/src/Dotnet.Script.DependencyModel.Nuget/NuGetMetadataReferenceResolver.cs index d773d9da..cd148457 100644 --- a/src/Dotnet.Script.DependencyModel.Nuget/NuGetMetadataReferenceResolver.cs +++ b/src/Dotnet.Script.DependencyModel.Nuget/NuGetMetadataReferenceResolver.cs @@ -21,7 +21,7 @@ public NuGetMetadataReferenceResolver(MetadataReferenceResolver metadataReferenc { _metadataReferenceResolver = metadataReferenceResolver; } - + public override bool Equals(object other) { return _metadataReferenceResolver.Equals(other); @@ -42,7 +42,7 @@ public override PortableExecutableReference ResolveMissingAssembly(MetadataRefer public override ImmutableArray ResolveReference(string reference, string baseFilePath, MetadataReferenceProperties properties) { - if (reference.StartsWith("nuget", StringComparison.OrdinalIgnoreCase)) + if (reference.StartsWith("nuget", StringComparison.OrdinalIgnoreCase) || reference.StartsWith("sdk", StringComparison.OrdinalIgnoreCase)) { // HACK We need to return something here to "mark" the reference as resolved. // https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/Portable/ReferenceManager/CommonReferenceManager.Resolution.cs#L838 diff --git a/src/Dotnet.Script.DependencyModel.Nuget/NuGetSourceReferenceResolver.cs b/src/Dotnet.Script.DependencyModel.Nuget/NuGetSourceReferenceResolver.cs index 500066c1..89075cfb 100644 --- a/src/Dotnet.Script.DependencyModel.Nuget/NuGetSourceReferenceResolver.cs +++ b/src/Dotnet.Script.DependencyModel.Nuget/NuGetSourceReferenceResolver.cs @@ -1,9 +1,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; -using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; +using Dotnet.Script.DependencyModel.ProjectSystem; namespace Dotnet.Script.DependencyModel.NuGet { @@ -15,7 +14,6 @@ public class NuGetSourceReferenceResolver : SourceReferenceResolver { private readonly SourceReferenceResolver _sourceReferenceResolver; private readonly IDictionary> _scriptMap; - private static readonly Regex PackageNameMatcher = new Regex(@"\s*nuget\s*:\s*(.*)\s*,", RegexOptions.Compiled | RegexOptions.IgnoreCase); public NuGetSourceReferenceResolver(SourceReferenceResolver sourceReferenceResolver, IDictionary> scriptMap) { @@ -25,7 +23,7 @@ public NuGetSourceReferenceResolver(SourceReferenceResolver sourceReferenceResol public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) return false; + if (obj is null) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return _sourceReferenceResolver.Equals(obj); @@ -48,9 +46,8 @@ public override string NormalizePath(string path, string baseFilePath) public override string ResolveReference(string path, string baseFilePath) { - if (path.StartsWith("nuget:", StringComparison.OrdinalIgnoreCase)) + if (ScriptParser.TryParseNuGetPackageReference(path, out var packageName, out _)) { - var packageName = PackageNameMatcher.Match(path).Groups[1].Value; if (_scriptMap.TryGetValue(packageName, out var scripts)) { if (scripts.Count == 1) @@ -66,9 +63,8 @@ public override string ResolveReference(string path, string baseFilePath) public override Stream OpenRead(string resolvedPath) { - if (resolvedPath.StartsWith("nuget:", StringComparison.OrdinalIgnoreCase)) + if (ScriptParser.TryParseNuGetPackageReference(resolvedPath, out var packageName, out _)) { - var packageName = PackageNameMatcher.Match(resolvedPath).Groups[1].Value; var scripts = _scriptMap[packageName]; if (scripts.Count == 1) { diff --git a/src/Dotnet.Script.DependencyModel/AssemblyProperties.cs b/src/Dotnet.Script.DependencyModel/AssemblyProperties.cs index 50be859b..210cb3df 100644 --- a/src/Dotnet.Script.DependencyModel/AssemblyProperties.cs +++ b/src/Dotnet.Script.DependencyModel/AssemblyProperties.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Dotnet.Script.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Dotnet.Script.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f9790935628475aa44c2b2025427926d965f49fb36c645f5aeb7c03bd6183d31d02f42add3b2cb3ec4b0e508c5cf02478b41008ac8a6a6db314eaca3d678caac419d95e6e85e6cfb16fd93fec0bed8bc707875126e577ee7c96efe15737679d1a4dd1affec9e8f5c1b6f6518d51bcd1d3718b8b2694853eef059328ca81f6ea9")] \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependency.cs b/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependency.cs index 8d81470b..9d8c7267 100644 --- a/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependency.cs +++ b/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependency.cs @@ -1,28 +1,25 @@ using System.Collections.Generic; -namespace Dotnet.Script.DependencyModel.Compilation +public class CompilationDependency { - public class CompilationDependency + public CompilationDependency(string name, string version, IReadOnlyList assemblyPaths, IReadOnlyList scripts) { - public CompilationDependency(string name, string version, IReadOnlyList assemblyPaths, IReadOnlyList scripts) - { - Name = name; - Version = version; - AssemblyPaths = assemblyPaths; - Scripts = scripts; - } + Name = name; + Version = version; + AssemblyPaths = assemblyPaths; + Scripts = scripts; + } - public string Name { get; } + public string Name { get; } - public string Version { get; } + public string Version { get; } - public IReadOnlyList AssemblyPaths { get; } + public IReadOnlyList AssemblyPaths { get; } - public IReadOnlyList Scripts { get; } + public IReadOnlyList Scripts { get; } - public override string ToString() - { - return $"Name: {Name} , Version: {Version}"; - } + public override string ToString() + { + return $"Name: {Name} , Version: {Version}"; } } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependencyResolver.cs b/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependencyResolver.cs index d0892603..136cb552 100644 --- a/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependencyResolver.cs +++ b/src/Dotnet.Script.DependencyModel/Compilation/CompilationDependencyResolver.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Dotnet.Script.DependencyModel.Context; +using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Process; using Dotnet.Script.DependencyModel.ProjectSystem; @@ -11,33 +12,29 @@ namespace Dotnet.Script.DependencyModel.Compilation { public class CompilationDependencyResolver { - private readonly Logger _logger; private readonly ScriptProjectProvider _scriptProjectProvider; private readonly ScriptDependencyContextReader _scriptDependencyContextReader; + private readonly ICompilationReferenceReader _compilationReferenceReader; + private readonly IRestorer _restorer; - public CompilationDependencyResolver(ScriptProjectProvider scriptProjectProvider, ScriptDependencyContextReader scriptDependencyContextReader, LogFactory logFactory) + public CompilationDependencyResolver(LogFactory logFactory) : this(new ScriptProjectProvider(logFactory), new ScriptDependencyContextReader(logFactory), new CompilationReferencesReader(logFactory), logFactory) { - _scriptProjectProvider = scriptProjectProvider; - _scriptDependencyContextReader = scriptDependencyContextReader; - _restorer = CreateRestorer(logFactory); } - public CompilationDependencyResolver(LogFactory logFactory) - : this - ( - new ScriptProjectProvider(logFactory), - new ScriptDependencyContextReader(logFactory), - logFactory - ) + public CompilationDependencyResolver(ScriptProjectProvider scriptProjectProvider, ScriptDependencyContextReader scriptDependencyContextReader, ICompilationReferenceReader compilationReferenceReader, LogFactory logFactory) { + _scriptProjectProvider = scriptProjectProvider; + this._scriptDependencyContextReader = scriptDependencyContextReader; + _compilationReferenceReader = compilationReferenceReader; + _restorer = CreateRestorer(logFactory); } public IEnumerable GetDependencies(string targetDirectory, IEnumerable scriptFiles, bool enableScriptNugetReferences, string defaultTargetFramework = "net46") { - var pathToProjectFile = _scriptProjectProvider.CreateProject(targetDirectory, scriptFiles,defaultTargetFramework, enableScriptNugetReferences); - _restorer.Restore(pathToProjectFile, packageSources: Array.Empty()); - var pathToAssetsFile = Path.Combine(Path.GetDirectoryName(pathToProjectFile), "obj", "project.assets.json"); + var projectFileInfo = _scriptProjectProvider.CreateProject(targetDirectory, scriptFiles, defaultTargetFramework, enableScriptNugetReferences); + _restorer.Restore(projectFileInfo, packageSources: Array.Empty()); + var pathToAssetsFile = Path.Combine(Path.GetDirectoryName(projectFileInfo.Path), "obj", "project.assets.json"); var dependencyContext = _scriptDependencyContextReader.ReadDependencyContext(pathToAssetsFile); var result = new List(); foreach (var scriptDependency in dependencyContext.Dependencies) @@ -45,13 +42,17 @@ public IEnumerable GetDependencies(string targetDirectory var compilationDependency = new CompilationDependency(scriptDependency.Name, scriptDependency.Version, scriptDependency.CompileTimeDependencyPaths, scriptDependency.ScriptPaths); result.Add(compilationDependency); } + + var compilationReferences = _compilationReferenceReader.Read(projectFileInfo); + result.Add(new CompilationDependency("Dotnet.Script.Default.Dependencies", "99.0", compilationReferences.Select(cr => cr.Path).ToArray(), Array.Empty())); + return result; } private static IRestorer CreateRestorer(LogFactory logFactory) { var commandRunner = new CommandRunner(logFactory); - return new ProfiledRestorer(new DotnetRestorer(commandRunner, logFactory),logFactory); + return new ProfiledRestorer(new DotnetRestorer(commandRunner, logFactory), logFactory); } } } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Compilation/CompilationReference.cs b/src/Dotnet.Script.DependencyModel/Compilation/CompilationReference.cs new file mode 100644 index 00000000..e7629277 --- /dev/null +++ b/src/Dotnet.Script.DependencyModel/Compilation/CompilationReference.cs @@ -0,0 +1,12 @@ +namespace Dotnet.Script.DependencyModel.Compilation +{ + public class CompilationReference + { + public CompilationReference(string path) + { + Path = path; + } + + public string Path { get; } + } +} \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Compilation/CompilationReferencesReader.cs b/src/Dotnet.Script.DependencyModel/Compilation/CompilationReferencesReader.cs new file mode 100644 index 00000000..4f0704ed --- /dev/null +++ b/src/Dotnet.Script.DependencyModel/Compilation/CompilationReferencesReader.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; +using Dotnet.Script.DependencyModel.Logging; +using Dotnet.Script.DependencyModel.Process; +using Dotnet.Script.DependencyModel.ProjectSystem; + +namespace Dotnet.Script.DependencyModel.Compilation +{ + /// + /// + /// + public class CompilationReferencesReader : ICompilationReferenceReader + { + private readonly CommandRunner _commandRunner; + private Logger _log; + + public CompilationReferencesReader(LogFactory logFactory) : this(new CommandRunner(logFactory), logFactory) + { + } + + public CompilationReferencesReader(CommandRunner commandRunner, LogFactory logFactory) + { + _log = logFactory.CreateLogger(); + _commandRunner = commandRunner; + } + + public IEnumerable Read(ProjectFileInfo projectFile) + { + const string outputDirectory = "compilation"; + var workingDirectory = Path.GetDirectoryName(projectFile.Path); + if (!Directory.Exists(Path.Combine(workingDirectory, outputDirectory))) + { + Directory.CreateDirectory(Path.Combine(workingDirectory, outputDirectory)); + } + // Copy the csproj file so that we don't interfere with the project.assets.json file + // used for execution. + string pathToCompilationProjectFile = Path.Combine(workingDirectory, outputDirectory, Path.GetFileName(projectFile.Path)); + File.Copy(projectFile.Path, pathToCompilationProjectFile, true); + + // We remove any third party package references since we are only after the framework assemblies here. + RemovePackageReferences(pathToCompilationProjectFile); + + var referencePathsFile = Path.Combine(workingDirectory, outputDirectory, "ReferencePaths.txt"); + if (File.Exists(referencePathsFile)) + { + File.Delete(referencePathsFile); + } + var exitCode = _commandRunner.Execute("dotnet", $"build \"{pathToCompilationProjectFile}\" /p:OutputType=Library -o {outputDirectory} --nologo", workingDirectory); + if (exitCode != 0) + { + throw new Exception($"Unable to read compilation dependencies for '{projectFile.Path}'. Make sure that all script files contains valid NuGet references"); + } + var referenceAssemblies = File.ReadAllLines(referencePathsFile); + var compilationReferences = referenceAssemblies.Select(ra => new CompilationReference(ra)).ToArray(); + return compilationReferences; + } + + private static void RemovePackageReferences(string projectFile) + { + var document = XDocument.Load(projectFile); + document.Descendants("PackageReference").Remove(); + document.Save(projectFile); + } + } +} \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Compilation/ICompilationReferenceReader.cs b/src/Dotnet.Script.DependencyModel/Compilation/ICompilationReferenceReader.cs new file mode 100644 index 00000000..0c42db68 --- /dev/null +++ b/src/Dotnet.Script.DependencyModel/Compilation/ICompilationReferenceReader.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using Dotnet.Script.DependencyModel.ProjectSystem; + +namespace Dotnet.Script.DependencyModel.Compilation +{ + public interface ICompilationReferenceReader + { + IEnumerable Read(ProjectFileInfo projectFile); + } +} \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Context/CachedRestorer.cs b/src/Dotnet.Script.DependencyModel/Context/CachedRestorer.cs index d1aa0b15..2cfdf59c 100644 --- a/src/Dotnet.Script.DependencyModel/Context/CachedRestorer.cs +++ b/src/Dotnet.Script.DependencyModel/Context/CachedRestorer.cs @@ -1,4 +1,5 @@ using System.IO; +using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.ProjectSystem; @@ -28,17 +29,18 @@ public CachedRestorer(IRestorer restorer, LogFactory logFactory) public bool CanRestore => _restorer.CanRestore; /// - public void Restore(string pathToProjectFile, string[] packageSources) + public void Restore(ProjectFileInfo projectFileInfo, string[] packageSources) { - var projectFile = new ProjectFile(File.ReadAllText(pathToProjectFile)); - var pathToCachedProjectFile = $"{pathToProjectFile}.cache"; + var projectFile = new ProjectFile(File.ReadAllText(projectFileInfo.Path)); + + var pathToCachedProjectFile = $"{projectFileInfo.Path}.cache"; if (File.Exists(pathToCachedProjectFile)) { - _logger.Debug($"Found cached csproj file at: {pathToCachedProjectFile}"); + var cachedProjectFile = new ProjectFile(File.ReadAllText(pathToCachedProjectFile)); if (projectFile.Equals(cachedProjectFile)) { - _logger.Debug($"Skipping restore. {pathToProjectFile} and {pathToCachedProjectFile} are identical."); + _logger.Debug($"Skipping restore. {projectFileInfo.Path} and {pathToCachedProjectFile} are identical."); return; } else @@ -55,7 +57,7 @@ public void Restore(string pathToProjectFile, string[] packageSources) void RestoreAndCacheProjectFile() { - _restorer.Restore(pathToProjectFile, packageSources); + _restorer.Restore(projectFileInfo, packageSources); if (projectFile.IsCacheable) { _logger.Debug($"Caching project file : {pathToCachedProjectFile}"); @@ -63,7 +65,7 @@ void RestoreAndCacheProjectFile() } else { - _logger.Warning($"Unable to cache {pathToProjectFile}. For caching and optimal performance, ensure that the script(s) references Nuget packages with a pinned version."); + _logger.Warning($"Unable to cache {projectFileInfo.Path}. For caching and optimal performance, ensure that the script(s) references Nuget packages with a pinned version."); } } } diff --git a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs index 53b54cb1..b3bcf621 100644 --- a/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs +++ b/src/Dotnet.Script.DependencyModel/Context/DotnetRestorer.cs @@ -1,7 +1,10 @@ using Dotnet.Script.DependencyModel.Environment; +using Dotnet.Script.DependencyModel.Internal; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Process; +using Dotnet.Script.DependencyModel.ProjectSystem; using System; +using System.IO; using System.Linq; namespace Dotnet.Script.DependencyModel.Context @@ -19,27 +22,40 @@ public DotnetRestorer(CommandRunner commandRunner, LogFactory logFactory) _scriptEnvironment = ScriptEnvironment.Default; } - public void Restore(string pathToProjectFile, string[] packageSources) + public void Restore(ProjectFileInfo projectFileInfo, string[] packageSources) { var packageSourcesArgument = CreatePackageSourcesArguments(); + var configFileArgument = CreateConfigFileArgument(); var runtimeIdentifier = _scriptEnvironment.RuntimeIdentifier; + var workingDirectory = Path.GetFullPath(Path.GetDirectoryName(projectFileInfo.Path)); - _logger.Debug($"Restoring {pathToProjectFile} using the dotnet cli. RuntimeIdentifier : {runtimeIdentifier}"); - var exitcode = _commandRunner.Execute("dotnet", $"restore \"{pathToProjectFile}\" -r {runtimeIdentifier} {packageSourcesArgument}"); - //var exitcode = _commandRunner.Execute("dotnet", $"restore \"{pathToProjectFile}\" {packageSourcesArgument}"); - if (exitcode != 0) + + _logger.Debug($"Restoring {projectFileInfo.Path} using the dotnet cli. RuntimeIdentifier : {runtimeIdentifier} NugetConfigFile: {projectFileInfo.NuGetConfigFile}"); + + var commandPath = "dotnet"; + var commandArguments = $"restore \"{projectFileInfo.Path}\" -r {runtimeIdentifier} {packageSourcesArgument} {configFileArgument}"; + var commandResult = _commandRunner.Capture(commandPath, commandArguments, workingDirectory); + if (commandResult.ExitCode != 0) { // We must throw here, otherwise we may incorrectly run with the old 'project.assets.json' - throw new Exception($"Unable to restore packages from '{pathToProjectFile}'. Make sure that all script files contains valid NuGet references"); + throw new Exception($"Unable to restore packages from '{projectFileInfo.Path}'{System.Environment.NewLine}Make sure that all script files contains valid NuGet references{System.Environment.NewLine}{System.Environment.NewLine}Details:{System.Environment.NewLine}{workingDirectory} : {commandPath} {commandArguments}{System.Environment.NewLine}{commandResult.StandardOut}"); } string CreatePackageSourcesArguments() { return packageSources.Length == 0 ? string.Empty - : packageSources.Select(s => $"-s {s}") + : packageSources.Select(s => $"-s {CommandLine.EscapeArgument(s)}") .Aggregate((current, next) => $"{current} {next}"); } + + string CreateConfigFileArgument() + { + return string.IsNullOrWhiteSpace(projectFileInfo.NuGetConfigFile) + ? string.Empty + : $"--configfile \"{projectFileInfo.NuGetConfigFile}\""; + + } } public bool CanRestore => true; diff --git a/src/Dotnet.Script.DependencyModel/Context/IRestorer.cs b/src/Dotnet.Script.DependencyModel/Context/IRestorer.cs index 6c00a428..72e0591b 100644 --- a/src/Dotnet.Script.DependencyModel/Context/IRestorer.cs +++ b/src/Dotnet.Script.DependencyModel/Context/IRestorer.cs @@ -1,4 +1,6 @@ -namespace Dotnet.Script.DependencyModel.Context +using Dotnet.Script.DependencyModel.ProjectSystem; + +namespace Dotnet.Script.DependencyModel.Context { /// /// Represents a class that is capable of restoring a project file. @@ -10,7 +12,8 @@ public interface IRestorer /// /// /// A list of packages sources to be used when restoring NuGet packages. - void Restore(string pathToProjectFile, string[] packageSources); + /// The path to the NuGet config file to be used when restoring. + void Restore(ProjectFileInfo projectFileInfo, string[] packageSources); /// /// Gets a value that indicates if this is available on the system. diff --git a/src/Dotnet.Script.DependencyModel/Context/ProfiledRestorer.cs b/src/Dotnet.Script.DependencyModel/Context/ProfiledRestorer.cs index f0024a48..5f7f9031 100644 --- a/src/Dotnet.Script.DependencyModel/Context/ProfiledRestorer.cs +++ b/src/Dotnet.Script.DependencyModel/Context/ProfiledRestorer.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using Dotnet.Script.DependencyModel.Logging; +using Dotnet.Script.DependencyModel.ProjectSystem; namespace Dotnet.Script.DependencyModel.Context { @@ -16,11 +17,11 @@ public ProfiledRestorer(IRestorer restorer, LogFactory logFactory) public bool CanRestore => _restorer.CanRestore; - public void Restore(string pathToProjectFile, string[] packageSources) + public void Restore(ProjectFileInfo projectFileInfo, string[] packageSources) { var stopwatch = Stopwatch.StartNew(); - _restorer.Restore(pathToProjectFile, packageSources); - _logger.Debug($"Restoring {pathToProjectFile} took {stopwatch.ElapsedMilliseconds}ms"); + _restorer.Restore(projectFileInfo, packageSources); + _logger.Debug($"Restoring {projectFileInfo.Path} took {stopwatch.ElapsedMilliseconds}ms"); } } } diff --git a/src/Dotnet.Script.DependencyModel/Context/ScriptDependencyContextReader.cs b/src/Dotnet.Script.DependencyModel/Context/ScriptDependencyContextReader.cs index 593d5087..cd78b179 100644 --- a/src/Dotnet.Script.DependencyModel/Context/ScriptDependencyContextReader.cs +++ b/src/Dotnet.Script.DependencyModel/Context/ScriptDependencyContextReader.cs @@ -2,14 +2,16 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; +using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.ScriptPackage; -using Microsoft.DotNet.PlatformAbstractions; using NuGet.Common; using NuGet.Packaging; using NuGet.ProjectModel; -using NuGet.RuntimeModel; +using NuGet.Versioning; +using Newtonsoft.Json.Linq; namespace Dotnet.Script.DependencyModel.Context { @@ -38,6 +40,7 @@ public ScriptDependencyContext ReadDependencyContext(string pathToAssetsFile) // Since we execute "dotnet restore -r [rid]" we get two targets in the lock file. // The second target is the one containing the runtime deps for the given RID. var target = GetLockFileTarget(lockFile); + var targetLibraries = target.Libraries; var packageFolders = lockFile.PackageFolders.Select(lfi => lfi.Path).ToArray(); var userPackageFolder = packageFolders.First(); @@ -47,19 +50,90 @@ public ScriptDependencyContext ReadDependencyContext(string pathToAssetsFile) List scriptDependencies = new List(); foreach (var targetLibrary in targetLibraries) { - var scriptDependency = CreateScriptDependency(targetLibrary.Name, targetLibrary.Version.ToString(), packageFolders, packagePathResolver, targetLibrary); - if (scriptDependency.CompileTimeDependencyPaths.Any() || + var scriptDependency = CreateScriptDependency(targetLibrary.Name, targetLibrary.Version.ToString(), packagePathResolver, targetLibrary); + if ( scriptDependency.NativeAssetPaths.Any() || scriptDependency.RuntimeDependencyPaths.Any() || + scriptDependency.CompileTimeDependencyPaths.Any() || scriptDependency.ScriptPaths.Any()) { scriptDependencies.Add(scriptDependency); } } + if (ScriptEnvironment.Default.NetCoreVersion.Major >= 3) + { + var netcoreAppRuntimeAssemblyLocation = Path.GetDirectoryName(typeof(object).Assembly.Location); + var netcoreAppRuntimeAssemblies = Directory.GetFiles(netcoreAppRuntimeAssemblyLocation, "*.dll").Where(IsAssembly).ToArray(); + var netCoreAppDependency = new ScriptDependency("Microsoft.NETCore.App", ScriptEnvironment.Default.NetCoreVersion.Version, netcoreAppRuntimeAssemblies, Array.Empty(), Array.Empty(), Array.Empty()); + scriptDependencies.Add(netCoreAppDependency); + if (HasAspNetCoreFrameworkReference(pathToAssetsFile)) + { + var aspNetCoreRuntimeInfo = GetAspNetCoreRuntimeInfo(netcoreAppRuntimeAssemblyLocation); + var aspNetCoreAppRuntimeAssemblies = Directory.GetFiles(aspNetCoreRuntimeInfo.aspNetCoreRuntimeAssemblyLocation, "*.dll").Where(IsAssembly).ToArray(); + var aspNetCoreAppDependency = new ScriptDependency("Microsoft.AspNetCore.App", aspNetCoreRuntimeInfo.aspNetCoreVersion, aspNetCoreAppRuntimeAssemblies, Array.Empty(), Array.Empty(), Array.Empty()); + scriptDependencies.Add(aspNetCoreAppDependency); + } + } return new ScriptDependencyContext(scriptDependencies.ToArray()); } + private bool HasAspNetCoreFrameworkReference(string pathToAssetsFile) + { + JObject assetsFile = JObject.Parse(File.ReadAllText(pathToAssetsFile)); + return assetsFile["project"]?["frameworks"]?[ScriptEnvironment.Default.TargetFramework]?["frameworkReferences"]?["Microsoft.AspNetCore.App"] != null; + } + + private static (string aspNetCoreRuntimeAssemblyLocation, string aspNetCoreVersion) GetAspNetCoreRuntimeInfo(string netcoreAppRuntimeAssemblyLocation) + { + var netCoreAppRuntimeVersion = Path.GetFileName(netcoreAppRuntimeAssemblyLocation); + if (!SemanticVersion.TryParse(netCoreAppRuntimeVersion, out var version)) + { + throw new InvalidOperationException($"Unable to parse netcore app version '{netCoreAppRuntimeVersion}'"); + } + var pathToSharedFolder = Path.GetFullPath(Path.Combine(netcoreAppRuntimeAssemblyLocation, "..", "..")); + + var pathToAspNetCoreRuntimeFolder = Directory.GetDirectories(pathToSharedFolder, "Microsoft.AspNetCore.App", SearchOption.TopDirectoryOnly).SingleOrDefault(); + if (string.IsNullOrWhiteSpace(pathToAspNetCoreRuntimeFolder)) + { + throw new InvalidOperationException($"Failed to resolve the path to 'Microsoft.AspNetCore.App' in {pathToSharedFolder}"); + } + + var aspNetCoreVersionsFolders = Directory.GetDirectories(pathToAspNetCoreRuntimeFolder).Select(folder => Path.GetFileName(folder)); + + var aspNetCoreVersions = new List(); + foreach (var aspNetCoreVersionsFolder in aspNetCoreVersionsFolders) + { + if (!SemanticVersion.TryParse(aspNetCoreVersionsFolder, out var aspNetCoreVersion)) + { + throw new InvalidOperationException($"Unable to parse Asp.Net version {aspNetCoreVersionsFolder}"); + } + else + { + aspNetCoreVersions.Add(aspNetCoreVersion); + } + } + + var latestAspNetCoreVersion = aspNetCoreVersions.Where(v => v.Major == version.Major).OrderBy(v => v).Last(); + + return (Path.Combine(pathToAspNetCoreRuntimeFolder, latestAspNetCoreVersion.ToNormalizedString()), latestAspNetCoreVersion.ToNormalizedString()); + } + + + private static bool IsAssembly(string file) + { + // https://docs.microsoft.com/en-us/dotnet/standard/assembly/identify + try + { + AssemblyName.GetAssemblyName(file); + return true; + } + catch (System.Exception) + { + return false; + } + } + private static LockFileTarget GetLockFileTarget(LockFile lockFile) { if (lockFile.Targets.Count < 2) @@ -85,7 +159,7 @@ private LockFile GetLockFile(string pathToAssetsFile) return lockFile; } - private ScriptDependency CreateScriptDependency(string name, string version, string[] packageFolders, FallbackPackagePathResolver packagePathResolver, LockFileTargetLibrary targetLibrary) + private ScriptDependency CreateScriptDependency(string name, string version, FallbackPackagePathResolver packagePathResolver, LockFileTargetLibrary targetLibrary) { var runtimeDependencyPaths = GetRuntimeDependencyPaths(packagePathResolver, targetLibrary); var compileTimeDependencyPaths = GetCompileTimeDependencyPaths(packagePathResolver, targetLibrary); @@ -114,8 +188,8 @@ private string[] GetScriptPaths(FallbackPackagePathResolver packagePathResolver, private string[] GetNativeAssetPaths(FallbackPackagePathResolver packagePathResolver, LockFileTargetLibrary targetLibrary) { - List nativeAssetPaths = new List(); - foreach (var runtimeTarget in targetLibrary.NativeLibraries) + var nativeAssetPaths = new List(); + foreach (var runtimeTarget in targetLibrary.NativeLibraries.Where(lfi => !lfi.Path.EndsWith("_._"))) { var fullPath = ResolveFullPath(packagePathResolver, targetLibrary.Name, targetLibrary.Version.ToString(), runtimeTarget.Path); nativeAssetPaths.Add(fullPath); @@ -126,7 +200,7 @@ private string[] GetNativeAssetPaths(FallbackPackagePathResolver packagePathReso private static string[] GetRuntimeDependencyPaths(FallbackPackagePathResolver packagePathResolver, LockFileTargetLibrary targetLibrary) { - List runtimeDependencyPaths = new List(); + var runtimeDependencyPaths = new List(); foreach (var lockFileItem in targetLibrary.RuntimeAssemblies.Where(lfi => !lfi.Path.EndsWith("_._"))) { diff --git a/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj b/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj index 32eb28a8..a0be1fb6 100644 --- a/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj +++ b/src/Dotnet.Script.DependencyModel/Dotnet.Script.DependencyModel.csproj @@ -1,32 +1,34 @@ - - + + netstandard2.0 dotnet-script dotnet-script Provides runtime and compilation dependency resolution for dotnet-script based scripts. MIT - https://github.com/filipw/dotnet-script - https://raw.githubusercontent.com/filipw/Strathweb.TypedRouting.AspNetCore/master/strathweb.png - https://github.com/filipw/dotnet-script.git + https://github.com/dotnet-script/dotnet-script + https://avatars.githubusercontent.com/u/113979420 + https://github.com/dotnet-script/dotnet-script.git git script;csx;csharp;roslyn;omnisharp - 0.9.0 + 1.6.0 latest + true + ../dotnet-script.snk - - + + + ScriptParser.cs + + - - - - + + - - + \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Environment/ScriptEnvironment.cs b/src/Dotnet.Script.DependencyModel/Environment/ScriptEnvironment.cs index 48040e34..2b6de442 100644 --- a/src/Dotnet.Script.DependencyModel/Environment/ScriptEnvironment.cs +++ b/src/Dotnet.Script.DependencyModel/Environment/ScriptEnvironment.cs @@ -29,7 +29,7 @@ public class ScriptEnvironment private ScriptEnvironment() { _netCoreVersion = new Lazy(GetNetCoreAppVersion); - _targetFramework = new Lazy(() => _netCoreVersion.Value == DotnetVersion.Unknown ? throw new PlatformNotSupportedException("Cannot determine .NET Core Version") : _netCoreVersion.Value.Tfm); + _targetFramework = new Lazy(() => _netCoreVersion.Value == DotnetVersion.Unknown ? "net472" : _netCoreVersion.Value.Tfm); _installLocation = new Lazy(GetInstallLocation); _platformIdentifier = new Lazy(GetPlatformIdentifier); _runtimeIdentifier = new Lazy(GetRuntimeIdentifier); @@ -74,9 +74,10 @@ private static string GetPlatformIdentifier() private static DotnetVersion GetNetCoreAppVersion() { - // https://github.com/dotnet/BenchmarkDotNet/blob/94863ab4d024eca04d061423e5aad498feff386b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs#L156 + GetNetCoreVersion(); + // https://github.com/dotnet/BenchmarkDotNet/blob/94863ab4d024eca04d061423e5aad498feff386b/src/BenchmarkDotNet/Portability/RuntimeInformation.cs#L156 var codeBase = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly.CodeBase; - var pattern = @"^.*Microsoft\.NETCore\.App\/(\d\.\d)(.*?)\/"; + var pattern = @"^.*Microsoft\.NETCore\.App\/(\d+\.\d+)(.*?)\/"; var match = Regex.Match(codeBase, pattern, RegexOptions.IgnoreCase); if (!match.Success) { @@ -88,6 +89,16 @@ private static DotnetVersion GetNetCoreAppVersion() return new DotnetVersion(version, $"netcoreapp{tfm}"); } + public static string GetNetCoreVersion() + { + var assembly = typeof(System.Runtime.GCSettings).GetTypeInfo().Assembly; + var assemblyPath = assembly.CodeBase.Split(new[] { '/', '\\' }, StringSplitOptions.RemoveEmptyEntries); + int netCoreAppIndex = Array.IndexOf(assemblyPath, "Microsoft.NETCore.App"); + if (netCoreAppIndex > 0 && netCoreAppIndex < assemblyPath.Length - 2) + return assemblyPath[netCoreAppIndex + 1]; + return null; + } + private static string GetInstallLocation() { return Path.GetDirectoryName(new Uri(typeof(ScriptEnvironment).GetTypeInfo().Assembly.CodeBase).LocalPath); @@ -122,10 +133,16 @@ private static string GetProcessArchitecture() private static string GetRuntimeIdentifier() { var platformIdentifier = GetPlatformIdentifier(); + +#if NET8_0 + return $"{platformIdentifier}-{GetProcessArchitecture()}"; +#endif + if (platformIdentifier == "osx" || platformIdentifier == "linux") { return $"{platformIdentifier}-{GetProcessArchitecture()}"; } + var runtimeIdentifier = RuntimeEnvironment.GetRuntimeIdentifier(); return runtimeIdentifier; } @@ -139,9 +156,21 @@ public DotnetVersion(string version, string tfm) { Version = version; Tfm = tfm; + + var versionMatch = Regex.Match(input: Version, pattern: @"^(\d+)(?:\.(\d+))?"); + if (versionMatch.Success && versionMatch.Groups[1].Success) + Major = int.Parse(versionMatch.Groups[1].Value); + if (versionMatch.Success && versionMatch.Groups[2].Success) + Minor = int.Parse(versionMatch.Groups[2].Value); + if (Major >= 5) + { + Tfm = $"net{Major}.{Minor}"; + } } public string Version { get; } public string Tfm { get; } + public int Major { get; } + public int Minor { get; } } } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Internal/CommandLine.cs b/src/Dotnet.Script.DependencyModel/Internal/CommandLine.cs new file mode 100644 index 00000000..d7a037ba --- /dev/null +++ b/src/Dotnet.Script.DependencyModel/Internal/CommandLine.cs @@ -0,0 +1,93 @@ +using System; +using System.Text; +using System.Text.RegularExpressions; + +namespace Dotnet.Script.DependencyModel.Internal +{ + /// + /// + /// Performs operations on instances that contain command line information. + /// + /// + /// Tip: a ready-to-use package with this functionality is available at https://www.nuget.org/packages/Gapotchenko.FX.Diagnostics.CommandLine. + /// + /// + /// + /// Available + /// + static class CommandLine + { + /// + /// Escapes and optionally quotes a command line argument. + /// + /// The command line argument. + /// The escaped and optionally quoted command line argument. + public static string EscapeArgument(string value) + { + if (value == null) + return null; + + int length = value.Length; + if (length == 0) + return string.Empty; + + var sb = new StringBuilder(); + Escape.AppendQuotedText(sb, value); + + if (sb.Length == length) + return value; + + return sb.ToString(); + } + + static class Escape + { + public static void AppendQuotedText(StringBuilder sb, string text) + { + bool quotingRequired = IsQuotingRequired(text); + if (quotingRequired) + sb.Append('"'); + + int numberOfQuotes = 0; + for (int i = 0; i < text.Length; i++) + { + if (text[i] == '"') + numberOfQuotes++; + } + + if (numberOfQuotes > 0) + { + if ((numberOfQuotes % 2) != 0) + throw new Exception("Command line parameter cannot contain an odd number of double quotes."); + text = text.Replace("\\\"", "\\\\\"").Replace("\"", "\\\""); + } + + sb.Append(text); + + if (quotingRequired && text.EndsWith("\\")) + sb.Append('\\'); + + if (quotingRequired) + sb.Append('"'); + } + + static bool IsQuotingRequired(string parameter) => + !AllowedUnquotedRegex.IsMatch(parameter) || + DefinitelyNeedQuotesRegex.IsMatch(parameter); + + static Regex m_CachedAllowedUnquotedRegex; + + static Regex AllowedUnquotedRegex => + m_CachedAllowedUnquotedRegex ??= new Regex( + @"^[a-z\\/:0-9\._\-+=]*$", + RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + + static Regex m_CachedDefinitelyNeedQuotesRegex; + + static Regex DefinitelyNeedQuotesRegex => + m_CachedDefinitelyNeedQuotesRegex ??= new Regex( + "[|><\\s,;\"]+", + RegexOptions.CultureInvariant); + } + } +} diff --git a/src/Dotnet.Script.DependencyModel/Logging/LogActionExtensions.cs b/src/Dotnet.Script.DependencyModel/Logging/LogActionExtensions.cs index ae2e9b4e..14dfe8ff 100644 --- a/src/Dotnet.Script.DependencyModel/Logging/LogActionExtensions.cs +++ b/src/Dotnet.Script.DependencyModel/Logging/LogActionExtensions.cs @@ -35,19 +35,21 @@ public static class LevelMapper private static Dictionary CreateMap() { - var map = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - map.Add("t", LogLevel.Trace); - map.Add("trace", LogLevel.Trace); - map.Add("d", LogLevel.Debug); - map.Add("debug", LogLevel.Debug); - map.Add("i", LogLevel.Info); - map.Add("info", LogLevel.Info); - map.Add("w", LogLevel.Warning); - map.Add("warning", LogLevel.Warning); - map.Add("e", LogLevel.Error); - map.Add("error", LogLevel.Error); - map.Add("c", LogLevel.Critical); - map.Add("critical", LogLevel.Critical); + var map = new Dictionary(StringComparer.InvariantCultureIgnoreCase) + { + { "t", LogLevel.Trace }, + { "trace", LogLevel.Trace }, + { "d", LogLevel.Debug }, + { "debug", LogLevel.Debug }, + { "i", LogLevel.Info }, + { "info", LogLevel.Info }, + { "w", LogLevel.Warning }, + { "warning", LogLevel.Warning }, + { "e", LogLevel.Error }, + { "error", LogLevel.Error }, + { "c", LogLevel.Critical }, + { "critical", LogLevel.Critical } + }; return map; } diff --git a/src/Dotnet.Script.DependencyModel/Process/CommandRunner.cs b/src/Dotnet.Script.DependencyModel/Process/CommandRunner.cs index a9f4751b..e13234ea 100644 --- a/src/Dotnet.Script.DependencyModel/Process/CommandRunner.cs +++ b/src/Dotnet.Script.DependencyModel/Process/CommandRunner.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; +using System.IO; using Dotnet.Script.DependencyModel.Logging; namespace Dotnet.Script.DependencyModel.Process @@ -13,25 +15,38 @@ public CommandRunner(LogFactory logFactory) _logger = logFactory.CreateLogger(); } - public int Execute(string commandPath, string arguments = null) + public int Execute(string commandPath, string arguments = null, string workingDirectory = null) { _logger.Debug($"Executing '{commandPath} {arguments}'"); - var startInformation = CreateProcessStartInfo(commandPath, arguments); + var startInformation = CreateProcessStartInfo(commandPath, arguments, workingDirectory); var process = CreateProcess(startInformation); RunAndWait(process); return process.ExitCode; } - private static ProcessStartInfo CreateProcessStartInfo(string commandPath, string arguments) + public CommandResult Capture(string commandPath, string arguments, string workingDirectory = null) + { + var startInformation = CreateProcessStartInfo(commandPath, arguments, workingDirectory); + var process = CreateProcess(startInformation); + process.Start(); + var standardOut = process.StandardOutput.ReadToEnd(); + var standardError = process.StandardError.ReadToEnd(); + process.WaitForExit(); + return new CommandResult(process.ExitCode, standardOut, standardError); + } + + private static ProcessStartInfo CreateProcessStartInfo(string commandPath, string arguments, string workingDirectory) { var startInformation = new ProcessStartInfo($"{commandPath}") { CreateNoWindow = true, Arguments = arguments ?? "", RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false + RedirectStandardError = true, + UseShellExecute = false, + WorkingDirectory = workingDirectory ?? System.Environment.CurrentDirectory }; + RemoveMsBuildEnvironmentVariables(startInformation.Environment); return startInformation; } @@ -53,7 +68,7 @@ private static void RunAndWait(System.Diagnostics.Process process) } private System.Diagnostics.Process CreateProcess(ProcessStartInfo startInformation) { - var process = new System.Diagnostics.Process {StartInfo = startInformation}; + var process = new System.Diagnostics.Process { StartInfo = startInformation }; process.OutputDataReceived += (s, e) => { if (!string.IsNullOrWhiteSpace(e.Data)) @@ -65,10 +80,33 @@ private System.Diagnostics.Process CreateProcess(ProcessStartInfo startInformati { if (!string.IsNullOrWhiteSpace(e.Data)) { - _logger.Debug(e.Data); + _logger.Error(e.Data); } }; return process; } } + + public class CommandResult + { + public CommandResult(int exitCode, string standardOut, string standardError) + { + ExitCode = exitCode; + StandardOut = standardOut; + StandardError = standardError; + } + public string StandardOut { get; } + public string StandardError { get; } + public int ExitCode { get; } + + public CommandResult EnsureSuccessfulExitCode(int success = 0) + { + if (ExitCode != success) + { + throw new InvalidOperationException(StandardError); + } + return this; + } + } + } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/FileUtils.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/FileUtils.cs index 571df4f5..b23a0d1a 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/FileUtils.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/FileUtils.cs @@ -1,16 +1,18 @@ -using Dotnet.Script.DependencyModel.Environment; -using System; +using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Text; +using Dotnet.Script.DependencyModel.Environment; +using SysEnvironment = System.Environment; namespace Dotnet.Script.DependencyModel.ProjectSystem { public static class FileUtils { - public static string CreateTempFolder(string targetDirectory) + public static string CreateTempFolder(string targetDirectory, string targetFramework) { - string pathToProjectDirectory = GetPathToTempFolder(targetDirectory); + string pathToProjectDirectory = Path.Combine(GetPathToScriptTempFolder(targetDirectory), targetFramework); if (!Directory.Exists(pathToProjectDirectory)) { @@ -20,14 +22,14 @@ public static string CreateTempFolder(string targetDirectory) return pathToProjectDirectory; } - public static string GetPathToTempFolder(string targetDirectory) + public static string GetPathToScriptTempFolder(string targetDirectory) { if (!Path.IsPathRooted(targetDirectory)) { throw new ArgumentOutOfRangeException(nameof(targetDirectory), "Must be a root path"); } - var tempDirectory = Path.GetTempPath(); + var tempDirectory = GetTempPath(); var pathRoot = Path.GetPathRoot(targetDirectory); var targetDirectoryWithoutRoot = targetDirectory.Substring(pathRoot.Length); if (pathRoot.Length > 0 && ScriptEnvironment.Default.IsWindows) @@ -41,8 +43,44 @@ public static string GetPathToTempFolder(string targetDirectory) targetDirectoryWithoutRoot = Path.Combine(driveLetter, targetDirectoryWithoutRoot); } - var pathToProjectDirectory = Path.Combine(tempDirectory, "scripts", targetDirectoryWithoutRoot); + var pathToProjectDirectory = Path.Combine(tempDirectory, "dotnet-script", targetDirectoryWithoutRoot); return pathToProjectDirectory; } + + public static string GetTempPath() + { + // prefer the custom env variable if set + var cachePath = SysEnvironment.GetEnvironmentVariable("DOTNET_SCRIPT_CACHE_LOCATION"); + + if (!string.IsNullOrEmpty(cachePath)) + { + // if the path is not absolute, make it relative to the current folder + if (!Path.IsPathRooted(cachePath)) + { + cachePath = Path.Combine(Directory.GetCurrentDirectory(), cachePath); + } + return cachePath; + } + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + // base dir relative to which user specific cache data files should be stored + cachePath = SysEnvironment.GetEnvironmentVariable("XDG_CACHE_HOME"); + + // if $XDG_CACHE_HOME is not set, $HOME/.cache should be used. + if (string.IsNullOrEmpty(cachePath)) + { + cachePath = Path.Combine(SysEnvironment.GetFolderPath(SysEnvironment.SpecialFolder.UserProfile), ".cache"); + } + + return cachePath; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return Path.Combine(SysEnvironment.GetFolderPath(SysEnvironment.SpecialFolder.UserProfile), "Library/Caches/"); + } + + return Path.GetTempPath(); + } } } diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/NuGetUtilities.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/NuGetUtilities.cs index d4a64735..7e5637e9 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/NuGetUtilities.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/NuGetUtilities.cs @@ -1,70 +1,14 @@ using NuGet.Configuration; -using System.Collections.Generic; +using System.Linq; namespace Dotnet.Script.DependencyModel.ProjectSystem { internal static class NuGetUtilities { - struct NuGetConfigSection - { - public string Name; - public HashSet KeysForPathValues; - public bool AreAllValuesPaths; - } - - static readonly NuGetConfigSection[] NuGetSections = - { - new NuGetConfigSection { Name = "config", KeysForPathValues = new HashSet { "globalPackagesFolder", "repositoryPath" } }, - new NuGetConfigSection { Name = "bindingRedirects" }, - new NuGetConfigSection { Name = "packageRestore" }, - new NuGetConfigSection { Name = "solution" }, - new NuGetConfigSection { Name = "packageSources", AreAllValuesPaths = true }, - new NuGetConfigSection { Name = "packageSourceCredentials" }, - new NuGetConfigSection { Name = "apikeys" }, - new NuGetConfigSection { Name = "disabledPackageSources" }, - new NuGetConfigSection { Name = "activePackageSource" }, - }; - - // Create a NuGet file containing all properties with resolved absolute paths - public static void CreateNuGetConfigFromLocation(string pathToEvaluate, string targetDirectory) + public static string GetNearestConfigPath(string pathToEvaluate) { var settings = Settings.LoadDefaultSettings(pathToEvaluate); - var target = new Settings(targetDirectory); - - var valuesToSet = new List(); - foreach (var section in NuGetSections) - { - // Resolve properly path values - valuesToSet.Clear(); - if (section.AreAllValuesPaths) - { - // All values are paths - var values = settings.GetSettingValues(section.Name, true); - valuesToSet.AddRange(values); - } - else - { - var values = settings.GetSettingValues(section.Name, false); - if (section.KeysForPathValues != null) - { - // Some values are path - foreach (var value in values) - { - if (section.KeysForPathValues.Contains(value.Key)) - { - var val = settings.GetValue(section.Name, value.Key, true); - value.Value = val; - } - - valuesToSet.Add(value); - } - } - else - // All values are not path - valuesToSet.AddRange(values); - } - target.SetValues(section.Name, valuesToSet); - } + return settings.GetConfigFilePaths().FirstOrDefault(); } } } diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/PackageVersion.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/PackageVersion.cs index 93becee9..8f484b0b 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/PackageVersion.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/PackageVersion.cs @@ -8,7 +8,38 @@ namespace Dotnet.Script.DependencyModel.ProjectSystem /// public class PackageVersion : IEquatable { - private static Regex IsPinnedRegex = new Regex(@"^(?>\[\d+[^,\]]+(? ::= "0" + // | + // | + // + // ::= + // | + // + // ::= "0" + // | + // + // ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" + + const string NumericPattern = @"(?:0|[1-9][0-9]*)"; + + // ::= + // | "-" + // | "+" + // | "-" "+" + // + // ::= "." "." + + const string MajorPlusVersionPattern = NumericPattern + @"(?:\." + NumericPattern + @")"; + const string VersionSuffixPattern = @"(?:[+-][\w][\w+.-]*)?"; + + private static readonly Regex IsPinnedRegex = + new Regex(@"^(?>\[" + MajorPlusVersionPattern + @"{1,4}" + VersionSuffixPattern + @"\]" + + @"|" + MajorPlusVersionPattern + @"{2,3}" + VersionSuffixPattern + @")$", + RegexOptions.Compiled); /// /// Initializes a new instance of the class. @@ -29,7 +60,7 @@ public PackageVersion(string version) /// /// Gets a value that indicates whether the is "pinned". /// - public bool IsPinned {get;} + public bool IsPinned { get; } /// public bool Equals(PackageVersion other) diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/ParseResult.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/ParseResult.cs index c0abecec..fc2a0924 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/ParseResult.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/ParseResult.cs @@ -1,17 +1,16 @@ using System.Collections.Generic; namespace Dotnet.Script.DependencyModel.ProjectSystem -{ +{ public class ParseResult { - public ParseResult(IReadOnlyCollection packageReferences, string targetFramework) + public ParseResult(IReadOnlyCollection packageReferences) { PackageReferences = packageReferences; - TargetFramework = targetFramework; } public IReadOnlyCollection PackageReferences { get; } - public string TargetFramework { get; } + public string Sdk { get; set; } } } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/ProjectFile.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/ProjectFile.cs index f4d49b3c..35738a71 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/ProjectFile.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/ProjectFile.cs @@ -31,7 +31,7 @@ public ProjectFile(string xmlContent) var packageReferenceElements = projectFileDocument.Descendants("PackageReference"); foreach (var packageReferenceElement in packageReferenceElements) { - PackageReferences.Add(new PackageReference(packageReferenceElement.Attribute("Include").Value,packageReferenceElement.Attribute("Version").Value)); + PackageReferences.Add(new PackageReference(packageReferenceElement.Attribute("Include").Value, packageReferenceElement.Attribute("Version").Value)); } var assemblyReferenceElements = projectFileDocument.Descendants("Reference"); @@ -39,6 +39,8 @@ public ProjectFile(string xmlContent) { AssemblyReferences.Add(new AssemblyReference(assemblyReference.Attribute("Include").Value)); } + + Sdk = projectFileDocument.Descendants("Project").Single().Attributes("Sdk").Single().Value; } /// @@ -49,7 +51,7 @@ public ProjectFile(string xmlContent) /// /// Gets a list of elements for this . /// - public HashSet PackageReferences { get ;} = new HashSet(); + public HashSet PackageReferences { get; } = new HashSet(); /// /// Gets a list of elements for this . @@ -59,11 +61,22 @@ public ProjectFile(string xmlContent) /// /// Gets or sets the target framework for this . /// - public string TargetFramework { get; set;} = ScriptEnvironment.Default.TargetFramework; + public string TargetFramework { get; set; } = ScriptEnvironment.Default.TargetFramework; + + /// + /// Gets the project SDK + /// + public string Sdk { get; set; } = "Microsoft.NET.Sdk"; public void Save(string pathToProjectFile) { var projectFileDocument = XDocument.Parse(ReadTemplate("csproj.template")); + if (!string.IsNullOrEmpty(Sdk)) + { + var projectElement = projectFileDocument.Descendants("Project").Single(); + projectElement.Attributes("Sdk").Single().Value = Sdk; + } + var itemGroupElement = projectFileDocument.Descendants("ItemGroup").Single(); foreach (var packageReference in PackageReferences) { @@ -83,19 +96,15 @@ public void Save(string pathToProjectFile) var targetFrameworkElement = projectFileDocument.Descendants("TargetFramework").Single(); targetFrameworkElement.Value = TargetFramework; - using (var fileStream = new FileStream(pathToProjectFile, FileMode.Create, FileAccess.Write)) - { - projectFileDocument.Save(fileStream); - } + using var fileStream = new FileStream(pathToProjectFile, FileMode.Create, FileAccess.Write); + projectFileDocument.Save(fileStream); } private static string ReadTemplate(string name) { var resourceStream = typeof(ProjectFile).GetTypeInfo().Assembly.GetManifestResourceStream($"Dotnet.Script.DependencyModel.ProjectSystem.{name}"); - using (var streamReader = new StreamReader(resourceStream)) - { - return streamReader.ReadToEnd(); - } + using var streamReader = new StreamReader(resourceStream); + return streamReader.ReadToEnd(); } /// @@ -105,7 +114,8 @@ public bool Equals(ProjectFile other) if (ReferenceEquals(this, other)) return true; return PackageReferences.SequenceEqual(other.PackageReferences) && AssemblyReferences.SequenceEqual(other.AssemblyReferences) - && TargetFramework.Equals(other.TargetFramework); + && TargetFramework.Equals(other.TargetFramework) + && Sdk.Equals(other.Sdk); } /// diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/ProjectFileInfo.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/ProjectFileInfo.cs new file mode 100644 index 00000000..1e8a549f --- /dev/null +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/ProjectFileInfo.cs @@ -0,0 +1,30 @@ +namespace Dotnet.Script.DependencyModel.ProjectSystem +{ + /// + /// Contains information about the generated project file and + /// where to find the "nearest" NuGet.Config file. + /// + public class ProjectFileInfo + { + /// + /// Initializes a new instance of the class. + /// + /// The path to the generated project file to be used during restore. + /// The path to the nearest NuGet.Config file seen from the target script folder. + public ProjectFileInfo(string path, string nugetConfigFile) + { + Path = path; + NuGetConfigFile = nugetConfigFile; + } + + /// + /// Gets the path of the generated project file to be used during restore. + /// + public string Path { get; } + + /// + /// Gets the path to the nearest NuGet.Config file seen from the target script folder. + /// + public string NuGetConfigFile { get; } + } +} \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptFilesResolver.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptFilesResolver.cs index 3049dc42..740acbad 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptFilesResolver.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptFilesResolver.cs @@ -60,13 +60,13 @@ private void Process(string csxFile, HashSet result) } private static string[] GetLoadDirectives(string content) - { - var matches = Regex.Matches(content, @"^\s*#load\s*""\s*(.+)\s*""", RegexOptions.Multiline); + { + var matches = Regex.Matches(content, @"^\s*#load\s*""\s*(.+?)\s*""", RegexOptions.Multiline); List result = new List(); foreach (var match in matches.Cast()) { var value = match.Groups[1].Value; - if (value.StartsWith("nuget", StringComparison.InvariantCultureIgnoreCase)) + if (value.StartsWith("nuget:", StringComparison.InvariantCultureIgnoreCase)) { continue; } diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptParser.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptParser.cs index 95e28474..9222a220 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptParser.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -6,7 +7,7 @@ namespace Dotnet.Script.DependencyModel.ProjectSystem { - public class ScriptParser + public partial class ScriptParser { private readonly Logger _logger; @@ -17,71 +18,62 @@ public ScriptParser(LogFactory logFactory) public ParseResult ParseFromCode(string code) { - string currentTargetFramework = null; var allPackageReferences = new HashSet(); allPackageReferences.UnionWith(ReadPackageReferencesFromReferenceDirective(code)); allPackageReferences.UnionWith(ReadPackageReferencesFromLoadDirective(code)); - string targetFramework = ReadTargetFramework(code); - if (targetFramework != null) + var sdkReference = ReadSdkFromReferenceDirective(code); + string sdk = string.Empty; + if (!string.IsNullOrWhiteSpace(sdkReference)) { - if (currentTargetFramework != null && targetFramework != currentTargetFramework) - { - _logger.Debug($"Found multiple target frameworks. Using {currentTargetFramework}."); - } - else - { - currentTargetFramework = targetFramework; - } + sdk = sdkReference; } - - return new ParseResult(allPackageReferences, currentTargetFramework); + return new ParseResult(allPackageReferences) { Sdk = sdk }; } public ParseResult ParseFromFiles(IEnumerable csxFiles) { var allPackageReferences = new HashSet(); - string currentTargetFramework = null; + string sdk = string.Empty; foreach (var csxFile in csxFiles) { _logger.Debug($"Parsing {csxFile}"); var fileContent = File.ReadAllText(csxFile); allPackageReferences.UnionWith(ReadPackageReferencesFromReferenceDirective(fileContent)); allPackageReferences.UnionWith(ReadPackageReferencesFromLoadDirective(fileContent)); - string targetFramework = ReadTargetFramework(fileContent); - if (targetFramework != null) + var sdkReference = ReadSdkFromReferenceDirective(fileContent); + if (!string.IsNullOrWhiteSpace(sdkReference)) { - if (currentTargetFramework != null && targetFramework != currentTargetFramework) - { - _logger.Debug($"Found multiple target frameworks. Using {currentTargetFramework}."); - } - else - { - currentTargetFramework = targetFramework; - } + sdk = sdkReference; } } - return new ParseResult(allPackageReferences, currentTargetFramework); + return new ParseResult(allPackageReferences) { Sdk = sdk }; } - const string Hws = @"[\x20\t]*"; // hws = horizontal whitespace - - const string DirectivePatternPrefix = @"^" - + Hws + @"#"; - const string DirectivePatternSuffix = Hws + @"""nuget:" - // https://github.com/NuGet/docs.microsoft.com-nuget/issues/543#issue-270039223 - + Hws + @"(\w+(?:[_.-]\w+)*)" - + @"(?:" + Hws + "," + Hws + @"(.+?))?"""; + private static string ReadSdkFromReferenceDirective(string fileContent) + { + const string pattern = DirectivePatternPrefix + "r" + SdkDirectivePatternSuffix; + var match = Regex.Match(fileContent, pattern, RegexOptions.Multiline); + if (match.Success) + { + var sdk = match.Groups[1].Value; + if (!string.Equals(sdk, "Microsoft.NET.Sdk.Web", System.StringComparison.InvariantCultureIgnoreCase)) + { + throw new NotSupportedException($"The sdk '{sdk}' is not supported. Currently 'Microsoft.NET.Sdk.Web' is the only sdk supported."); + } + } + return match.Success ? match.Groups[1].Value : string.Empty; + } private static IEnumerable ReadPackageReferencesFromReferenceDirective(string fileContent) { - const string pattern = DirectivePatternPrefix + "r" + DirectivePatternSuffix; + const string pattern = DirectivePatternPrefix + "r" + NuGetDirectivePatternSuffix; return ReadPackageReferencesFromDirective(pattern, fileContent); } private static IEnumerable ReadPackageReferencesFromLoadDirective(string fileContent) { - const string pattern = DirectivePatternPrefix + "load" + DirectivePatternSuffix; + const string pattern = DirectivePatternPrefix + "load" + NuGetDirectivePatternSuffix; return ReadPackageReferencesFromDirective(pattern, fileContent); } @@ -98,16 +90,5 @@ private static IEnumerable ReadPackageReferencesFromDirective( yield return packageReference; } } - - private static string ReadTargetFramework(string fileContent) - { - const string pattern = @"^" + Hws + @"#!" + Hws + @"""(.*)"""; - var match = Regex.Match(fileContent, pattern); - if (match.Success) - { - return match.Groups[1].Value; - } - return null; - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptParserInternal.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptParserInternal.cs new file mode 100644 index 00000000..72c3fb5c --- /dev/null +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptParserInternal.cs @@ -0,0 +1,37 @@ +using System.Text.RegularExpressions; + +namespace Dotnet.Script.DependencyModel.ProjectSystem +{ + partial class ScriptParser + { + const string Hws = @"[\x20\t]*"; // hws = horizontal whitespace + + const string NuGetPattern = @"nuget:" + // https://github.com/NuGet/docs.microsoft.com-nuget/issues/543#issue-270039223 + + Hws + @"(\w+(?:[_.-]\w+)*)" + + @"(?:" + Hws + "," + Hws + @"(.+?))?"; + + const string SdkPattern = @"sdk:" + + Hws + @"(\w+(?:[_.-]\w+)*)" + + @"(?:" + Hws + @")?"; + + const string WholeNuGetPattern = @"^" + NuGetPattern + @"$"; + + const string DirectivePatternPrefix = @"^" + Hws + @"#"; + const string NuGetDirectivePatternSuffix = Hws + @"""" + NuGetPattern + @""""; + + const string SdkDirectivePatternSuffix = Hws + @"""" + SdkPattern + @""""; + + internal static bool TryParseNuGetPackageReference(string input, + out string id, out string version) + { + bool success; + (success, id, version) = + Regex.Match(input, WholeNuGetPattern, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase) + is { } match && match.Success + ? (true, match.Groups[1].Value, match.Groups[2].Value) + : default; + return success; + } + } +} diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptProjectProvider.cs b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptProjectProvider.cs index 7970019f..a050c64f 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptProjectProvider.cs +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/ScriptProjectProvider.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.IO; using System.Linq; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; -using NuGet.Configuration; namespace Dotnet.Script.DependencyModel.ProjectSystem { @@ -27,7 +25,7 @@ private ScriptProjectProvider(ScriptParser scriptParser, ScriptFilesResolver scr { } - public string CreateProjectForRepl(string code, string targetDirectory, string defaultTargetFramework = "net46") + public ProjectFileInfo CreateProjectForRepl(string code, string targetDirectory, string defaultTargetFramework = "net46") { var scriptFiles = _scriptFilesResolver.GetScriptFilesFromCode(code); targetDirectory = Path.Combine(targetDirectory, "interactive"); @@ -47,7 +45,7 @@ public string CreateProjectForRepl(string code, string targetDirectory, string d } targetDirectory = Path.Combine(targetDirectory, "interactive"); - var pathToProjectFile = GetPathToProjectFile(targetDirectory); + var pathToProjectFile = GetPathToProjectFile(targetDirectory, defaultTargetFramework); var projectFile = new ProjectFile(); foreach (var packageReference in allPackageReferences) @@ -55,14 +53,14 @@ public string CreateProjectForRepl(string code, string targetDirectory, string d projectFile.PackageReferences.Add(packageReference); } - projectFile.TargetFramework = parseResultFromCode.TargetFramework ?? defaultTargetFramework; + projectFile.TargetFramework = defaultTargetFramework; + projectFile.Sdk = parseResultFromCode.Sdk; projectFile.Save(pathToProjectFile); LogProjectFileInfo(pathToProjectFile); - EvaluateAndGenerateNuGetConfigFile(targetDirectory, Path.GetDirectoryName(pathToProjectFile)); - return pathToProjectFile; + return new ProjectFileInfo(pathToProjectFile, NuGetUtilities.GetNearestConfigPath(targetDirectory)); } private void LogProjectFileInfo(string pathToProjectFile) @@ -72,12 +70,12 @@ private void LogProjectFileInfo(string pathToProjectFile) _logger.Debug(content); } - public string CreateProject(string targetDirectory, string defaultTargetFramework = "net46", bool enableNuGetScriptReferences = false) + public ProjectFileInfo CreateProject(string targetDirectory, string defaultTargetFramework = "net46", bool enableNuGetScriptReferences = false) { return CreateProject(targetDirectory, Directory.GetFiles(targetDirectory, "*.csx", SearchOption.AllDirectories), defaultTargetFramework, enableNuGetScriptReferences); } - public string CreateProject(string targetDirectory, IEnumerable scriptFiles, string defaultTargetFramework = "net46", bool enableNuGetScriptReferences = false) + public ProjectFileInfo CreateProject(string targetDirectory, IEnumerable scriptFiles, string defaultTargetFramework = "net46", bool enableNuGetScriptReferences = false) { if (scriptFiles == null || !scriptFiles.Any()) { @@ -95,56 +93,48 @@ public string CreateProject(string targetDirectory, IEnumerable scriptFi return SaveProjectFileFromScriptFiles(targetDirectory, defaultTargetFramework, scriptFiles.ToArray()); } - public string CreateProjectForScriptFile(string scriptFile) + public ProjectFileInfo CreateProjectForScriptFile(string scriptFile) { _logger.Debug($"Creating project file for {scriptFile}"); var scriptFiles = _scriptFilesResolver.GetScriptFiles(scriptFile); return SaveProjectFileFromScriptFiles(Path.GetDirectoryName(scriptFile), _scriptEnvironment.TargetFramework, scriptFiles.ToArray()); } - private string SaveProjectFileFromScriptFiles(string targetDirectory, string defaultTargetFramework, string[] csxFiles) + private ProjectFileInfo SaveProjectFileFromScriptFiles(string targetDirectory, string defaultTargetFramework, string[] csxFiles) { ProjectFile projectFile = CreateProjectFileFromScriptFiles(defaultTargetFramework, csxFiles); - var pathToProjectFile = GetPathToProjectFile(targetDirectory); + var pathToProjectFile = GetPathToProjectFile(targetDirectory, defaultTargetFramework); projectFile.Save(pathToProjectFile); LogProjectFileInfo(pathToProjectFile); - EvaluateAndGenerateNuGetConfigFile(targetDirectory, Path.GetDirectoryName(pathToProjectFile)); - return pathToProjectFile; + return new ProjectFileInfo(pathToProjectFile, NuGetUtilities.GetNearestConfigPath(targetDirectory)); } public ProjectFile CreateProjectFileFromScriptFiles(string defaultTargetFramework, string[] csxFiles) { - var parseresult = _scriptParser.ParseFromFiles(csxFiles); + var parseResult = _scriptParser.ParseFromFiles(csxFiles); var projectFile = new ProjectFile(); - foreach (var packageReference in parseresult.PackageReferences) + foreach (var packageReference in parseResult.PackageReferences) { projectFile.PackageReferences.Add(packageReference); } - projectFile.TargetFramework = parseresult.TargetFramework ?? defaultTargetFramework; + projectFile.TargetFramework = defaultTargetFramework; + projectFile.Sdk = parseResult.Sdk; return projectFile; } - private void EvaluateAndGenerateNuGetConfigFile(string targetDirectory, string pathToProjectFileFolder) - { - var pathToDestinationNuGetConfigFile = Path.Combine(pathToProjectFileFolder, Settings.DefaultSettingsFileName); - - if (File.Exists(pathToDestinationNuGetConfigFile)) - File.Delete(pathToDestinationNuGetConfigFile); - - _logger.Debug($"Generating NuGet config evaluated at {targetDirectory} to {pathToDestinationNuGetConfigFile}"); - NuGetUtilities.CreateNuGetConfigFromLocation(targetDirectory, pathToProjectFileFolder); - } - public static string GetPathToProjectFile(string targetDirectory) + public static string GetPathToProjectFile(string targetDirectory, string targetFramework, string projectName = null) { - var pathToProjectDirectory = FileUtils.CreateTempFolder(targetDirectory); - var pathToProjectFile = Path.Combine(pathToProjectDirectory, "script.csproj"); + projectName ??= "script"; + var projectFileName = projectName + ".csproj"; + var pathToProjectDirectory = FileUtils.CreateTempFolder(targetDirectory, targetFramework); + var pathToProjectFile = Path.Combine(pathToProjectDirectory, projectFileName); return pathToProjectFile; } } diff --git a/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template b/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template index 0f45e033..8bfbcffb 100644 --- a/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template +++ b/src/Dotnet.Script.DependencyModel/ProjectSystem/csproj.template @@ -1,9 +1,13 @@  Exe - netcoreapp2.1 + net5.0 latest + true + + + \ No newline at end of file diff --git a/src/Dotnet.Script.DependencyModel/Runtime/RuntimeDependencyResolver.cs b/src/Dotnet.Script.DependencyModel/Runtime/RuntimeDependencyResolver.cs index 9efd3cfd..7cc1c86e 100644 --- a/src/Dotnet.Script.DependencyModel/Runtime/RuntimeDependencyResolver.cs +++ b/src/Dotnet.Script.DependencyModel/Runtime/RuntimeDependencyResolver.cs @@ -13,43 +13,39 @@ namespace Dotnet.Script.DependencyModel.Runtime public class RuntimeDependencyResolver { private readonly ScriptProjectProvider _scriptProjectProvider; - private readonly ScriptEnvironment _scriptEnvironment; - private readonly ScriptDependencyContextReader _dependencyContextReader; private readonly IRestorer _restorer; - public RuntimeDependencyResolver(ScriptProjectProvider scriptProjectProvider, LogFactory logFactory, ScriptEnvironment scriptEnvironment, bool useRestoreCache) + public RuntimeDependencyResolver(ScriptProjectProvider scriptProjectProvider, LogFactory logFactory, bool useRestoreCache) { _scriptProjectProvider = scriptProjectProvider; - _scriptEnvironment = scriptEnvironment; _dependencyContextReader = new ScriptDependencyContextReader(logFactory); _restorer = CreateRestorer(logFactory, useRestoreCache); } - public RuntimeDependencyResolver(LogFactory logFactory, bool useRestoreCache) : this(new ScriptProjectProvider(logFactory), logFactory, ScriptEnvironment.Default, useRestoreCache) - { - - } + public RuntimeDependencyResolver(LogFactory logFactory, bool useRestoreCache) : this(new ScriptProjectProvider(logFactory), logFactory, useRestoreCache) + { + } private static IRestorer CreateRestorer(LogFactory logFactory, bool useRestoreCache) { var commandRunner = new CommandRunner(logFactory); if (useRestoreCache) { - return new ProfiledRestorer(new CachedRestorer(new DotnetRestorer(commandRunner, logFactory),logFactory),logFactory); + return new ProfiledRestorer(new CachedRestorer(new DotnetRestorer(commandRunner, logFactory), logFactory), logFactory); } else { - return new ProfiledRestorer(new DotnetRestorer(commandRunner, logFactory),logFactory); + return new ProfiledRestorer(new DotnetRestorer(commandRunner, logFactory), logFactory); } } public IEnumerable GetDependencies(string scriptFile, string[] packageSources) { - var pathToProjectFile = _scriptProjectProvider.CreateProjectForScriptFile(scriptFile); - _restorer.Restore(pathToProjectFile, packageSources); - var pathToAssetsFile = Path.Combine(Path.GetDirectoryName(pathToProjectFile), "obj", "project.assets.json"); + var projectFileInfo = _scriptProjectProvider.CreateProjectForScriptFile(scriptFile); + _restorer.Restore(projectFileInfo, packageSources); + var pathToAssetsFile = Path.Combine(Path.GetDirectoryName(projectFileInfo.Path), "obj", "project.assets.json"); return GetDependenciesInternal(pathToAssetsFile); } @@ -61,9 +57,9 @@ public IEnumerable GetDependenciesForLibrary(string pathToLib public IEnumerable GetDependenciesForCode(string targetDirectory, ScriptMode scriptMode, string[] packageSources, string code = null) { - var pathToProjectFile = _scriptProjectProvider.CreateProjectForRepl(code, Path.Combine(targetDirectory, scriptMode.ToString()), ScriptEnvironment.Default.TargetFramework); - _restorer.Restore(pathToProjectFile, packageSources); - var pathToAssetsFile = Path.Combine(Path.GetDirectoryName(pathToProjectFile), "obj", "project.assets.json"); + var projectFileInfo = _scriptProjectProvider.CreateProjectForRepl(code, Path.Combine(targetDirectory, scriptMode.ToString()), ScriptEnvironment.Default.TargetFramework); + _restorer.Restore(projectFileInfo, packageSources); + var pathToAssetsFile = Path.Combine(Path.GetDirectoryName(projectFileInfo.Path), "obj", "project.assets.json"); return GetDependenciesInternal(pathToAssetsFile); } @@ -74,7 +70,7 @@ private IEnumerable GetDependenciesInternal(string pathToAsse foreach (var scriptDependency in context.Dependencies) { var runtimeAssemblies = scriptDependency.RuntimeDependencyPaths.Select(rdp => new RuntimeAssembly(AssemblyName.GetAssemblyName(rdp), rdp)).ToList(); - var runtimeDependency = new RuntimeDependency(scriptDependency.Name, scriptDependency.Version,runtimeAssemblies, scriptDependency.NativeAssetPaths,scriptDependency.ScriptPaths); + var runtimeDependency = new RuntimeDependency(scriptDependency.Name, scriptDependency.Version, runtimeAssemblies, scriptDependency.NativeAssetPaths, scriptDependency.ScriptPaths); result.Add(runtimeDependency); } diff --git a/src/Dotnet.Script.DependencyModel/ScriptPackage/ScriptFilesDependencyResolver.cs b/src/Dotnet.Script.DependencyModel/ScriptPackage/ScriptFilesDependencyResolver.cs index 2b80e5ef..642f73a3 100644 --- a/src/Dotnet.Script.DependencyModel/ScriptPackage/ScriptFilesDependencyResolver.cs +++ b/src/Dotnet.Script.DependencyModel/ScriptPackage/ScriptFilesDependencyResolver.cs @@ -109,9 +109,9 @@ private static IDictionary> GetScriptFilesPerTargetFramewor if (match.Success) { var targetFramework = match.Groups[1].Value; - if (!result.TryGetValue(targetFramework, out var files)) + if (!result.TryGetValue(targetFramework, out _)) { - files = new List(); + var files = new List(); result.Add(targetFramework, files); } result[targetFramework].Add(match.Groups[0].Value); @@ -125,22 +125,5 @@ private static string GetRootPath(string pathToScriptFile) var match = RootPathMatcher.Match(pathToScriptFile); return match.Groups[1].Value; } - - private static string GetPackageFullPath(string packagePath, string[] nugetPackageFolders) - { - foreach (var nugetPackageFolder in nugetPackageFolders) - { - var packageFullPath = Path.Combine(nugetPackageFolder, packagePath); - if (Directory.Exists(packageFullPath)) - { - return packageFullPath; - } - } - - string message = $@"The requested script package path ({packagePath}) was not found in the global Nuget cache(s). -. Try executing/publishing the script again with the '--no-cache' option"; - - throw new InvalidOperationException(message); - } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Desktop.Tests/CompilationDepenencyTests.cs b/src/Dotnet.Script.Desktop.Tests/CompilationDepenencyTests.cs new file mode 100644 index 00000000..e20e33e0 --- /dev/null +++ b/src/Dotnet.Script.Desktop.Tests/CompilationDepenencyTests.cs @@ -0,0 +1,35 @@ +using System.IO; +using System.Linq; +using Dotnet.Script.DependencyModel.Compilation; +using Dotnet.Script.Shared.Tests; +using Xunit; +using Xunit.Abstractions; + +namespace Dotnet.Script.Desktop.Tests +{ + public class CompilationDependencyTests + { + public CompilationDependencyTests(ITestOutputHelper testOutputHelper) + { + testOutputHelper.Capture(); + } + + [Theory] + [InlineData("net6.0")] + [InlineData("net7.0")] + public void ShouldGetCompilationDependenciesForNetCoreApp(string targetFramework) + { + var resolver = CreateResolver(); + var targetDirectory = TestPathUtils.GetPathToTestFixtureFolder("HelloWorld"); + var csxFiles = Directory.GetFiles(targetDirectory, "*.csx"); + var dependencies = resolver.GetDependencies(targetDirectory, csxFiles, true, targetFramework); + Assert.True(dependencies.Count() > 0); + } + + private CompilationDependencyResolver CreateResolver() + { + var resolver = new CompilationDependencyResolver(TestOutputHelper.CreateTestLogFactory()); + return resolver; + } + } +} \ No newline at end of file diff --git a/src/Dotnet.Script.Desktop.Tests/Dotnet.Script.Desktop.Tests.csproj b/src/Dotnet.Script.Desktop.Tests/Dotnet.Script.Desktop.Tests.csproj index 7c2fd1b5..5d609576 100644 --- a/src/Dotnet.Script.Desktop.Tests/Dotnet.Script.Desktop.Tests.csproj +++ b/src/Dotnet.Script.Desktop.Tests/Dotnet.Script.Desktop.Tests.csproj @@ -1,31 +1,29 @@ + - - net461 + net472 + true + ../dotnet-script.snk - - - - - - - + + + + all runtime; build; native; contentfiles; analyzers - + + + - - PreserveNewest - - + \ No newline at end of file diff --git a/src/Dotnet.Script.Desktop.Tests/InteractiveRunnerTests.cs b/src/Dotnet.Script.Desktop.Tests/InteractiveRunnerTests.cs index fd52baa3..561605cd 100644 --- a/src/Dotnet.Script.Desktop.Tests/InteractiveRunnerTests.cs +++ b/src/Dotnet.Script.Desktop.Tests/InteractiveRunnerTests.cs @@ -10,7 +10,6 @@ public class InteractiveRunnerTests : InteractiveRunnerTestsBase { public InteractiveRunnerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { - ScriptEnvironment.Default.OverrideTargetFramework("net461"); } } } diff --git a/src/Dotnet.Script.Extras/Dotnet.Script.Extras.csproj b/src/Dotnet.Script.Extras/Dotnet.Script.Extras.csproj index a14b67b6..c79a5f84 100644 --- a/src/Dotnet.Script.Extras/Dotnet.Script.Extras.csproj +++ b/src/Dotnet.Script.Extras/Dotnet.Script.Extras.csproj @@ -1,8 +1,8 @@ - - + + Extensions and add ons to C# scripting - 0.2.0 + 0.3.0 netstandard2.0 Dotnet.Script.Extras Dotnet.Script.Extras @@ -10,10 +10,8 @@ false false - - - + + - - + \ No newline at end of file diff --git a/src/Dotnet.Script.Shared.Tests/Dotnet.Script.Shared.Tests.csproj b/src/Dotnet.Script.Shared.Tests/Dotnet.Script.Shared.Tests.csproj index 0c609055..2365de7b 100644 --- a/src/Dotnet.Script.Shared.Tests/Dotnet.Script.Shared.Tests.csproj +++ b/src/Dotnet.Script.Shared.Tests/Dotnet.Script.Shared.Tests.csproj @@ -1,16 +1,15 @@ + - netstandard2.0 + true + ../dotnet-script.snk - - + - - - + \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/FileUtils.cs b/src/Dotnet.Script.Shared.Tests/FileUtils.cs similarity index 97% rename from src/Dotnet.Script.Tests/FileUtils.cs rename to src/Dotnet.Script.Shared.Tests/FileUtils.cs index 214d19a5..f053a3ed 100644 --- a/src/Dotnet.Script.Tests/FileUtils.cs +++ b/src/Dotnet.Script.Shared.Tests/FileUtils.cs @@ -1,7 +1,7 @@ using System; using System.IO; -namespace Dotnet.Script.Tests +namespace Dotnet.Script.Shared.Tests { public class FileUtils { diff --git a/src/Dotnet.Script.Shared.Tests/InteractiveRunnerTestsBase.cs b/src/Dotnet.Script.Shared.Tests/InteractiveRunnerTestsBase.cs index f6a90d74..14996185 100644 --- a/src/Dotnet.Script.Shared.Tests/InteractiveRunnerTestsBase.cs +++ b/src/Dotnet.Script.Shared.Tests/InteractiveRunnerTestsBase.cs @@ -17,7 +17,7 @@ public InteractiveRunnerTestsBase(ITestOutputHelper testOutputHelper) testOutputHelper.Capture(); } - private class InteractiveTestContext + protected class InteractiveTestContext { public InteractiveTestContext(ScriptConsole console, InteractiveRunner runner) { @@ -29,7 +29,7 @@ public InteractiveTestContext(ScriptConsole console, InteractiveRunner runner) public InteractiveRunner Runner { get; } } - private InteractiveTestContext GetRunner(params string[] commands) + protected InteractiveTestContext GetRunner(params string[] commands) { var reader = new StringReader(string.Join(Environment.NewLine, commands)); var writer = new StringWriter(); diff --git a/src/Dotnet.Script.Tests/ProcessHelper.cs b/src/Dotnet.Script.Shared.Tests/ProcessHelper.cs similarity index 62% rename from src/Dotnet.Script.Tests/ProcessHelper.cs rename to src/Dotnet.Script.Shared.Tests/ProcessHelper.cs index 65400ae9..4f79d77e 100644 --- a/src/Dotnet.Script.Tests/ProcessHelper.cs +++ b/src/Dotnet.Script.Shared.Tests/ProcessHelper.cs @@ -2,11 +2,11 @@ using System.Diagnostics; using System.IO; -namespace Dotnet.Script.Tests +namespace Dotnet.Script.Shared.Tests { public static class ProcessHelper { - public static (string output, int exitcode) RunAndCaptureOutput(string fileName, string arguments, string workingDirectory = null) + public static ProcessResult RunAndCaptureOutput(string fileName, string arguments, string workingDirectory = null) { var startInfo = new ProcessStartInfo(fileName, arguments) { @@ -29,15 +29,17 @@ public static (string output, int exitcode) RunAndCaptureOutput(string fileName, catch { Console.WriteLine($"Failed to launch '{fileName}' with args, '{arguments}'"); - return (null, -1); + return new ProcessResult(null, -1, null, null); } - var output = process.StandardOutput.ReadToEnd(); - output += process.StandardError.ReadToEnd(); + var standardOut = process.StandardOutput.ReadToEnd().Trim(); + var standardError = process.StandardError.ReadToEnd().Trim(); + + var output = standardOut + standardError; process.WaitForExit(); - return (output.Trim(), process.ExitCode); + return new ProcessResult(output, process.ExitCode, standardOut, standardError); } } } diff --git a/src/Dotnet.Script.Shared.Tests/ProcessResult.cs b/src/Dotnet.Script.Shared.Tests/ProcessResult.cs new file mode 100644 index 00000000..ddd0e8c9 --- /dev/null +++ b/src/Dotnet.Script.Shared.Tests/ProcessResult.cs @@ -0,0 +1,24 @@ +namespace Dotnet.Script.Shared.Tests +{ + public class ProcessResult + { + public ProcessResult(string output, int exitCode, string standardOut, string standardError) + { + this.Output = output; + this.ExitCode = exitCode; + this.StandardOut = standardOut; + this.StandardError = standardError; + } + + public string Output { get; } + public int ExitCode { get; } + public string StandardOut { get; } + public string StandardError { get; } + + public void Deconstruct(out string output, out int exitCode) + { + output = this.Output; + exitCode = this.ExitCode; + } + } +} \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestPathUtils.cs b/src/Dotnet.Script.Shared.Tests/TestPathUtils.cs similarity index 57% rename from src/Dotnet.Script.Tests/TestPathUtils.cs rename to src/Dotnet.Script.Shared.Tests/TestPathUtils.cs index 5709c21f..5a6094d2 100644 --- a/src/Dotnet.Script.Tests/TestPathUtils.cs +++ b/src/Dotnet.Script.Shared.Tests/TestPathUtils.cs @@ -3,19 +3,20 @@ using System.Linq; using System.Text.RegularExpressions; -namespace Dotnet.Script.Tests +namespace Dotnet.Script.Shared.Tests { public class TestPathUtils { public static string GetPathToTestFixtureFolder(string fixture) { - var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; - return Path.Combine(baseDirectory, "..", "..", "..", "TestFixtures", fixture); + + var baseDirectory = Path.GetDirectoryName(new Uri(typeof(TestPathUtils).Assembly.CodeBase).LocalPath); + return Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", "..", "Dotnet.Script.Tests", "TestFixtures", fixture)); } public static string GetPathToTempFolder(string path) { - return DependencyModel.ProjectSystem.FileUtils.GetPathToTempFolder(path); + return DependencyModel.ProjectSystem.FileUtils.GetPathToScriptTempFolder(path); } public static string GetPathToScriptPackages(string fixture) @@ -33,16 +34,19 @@ public static string GetPathToTestFixture(string fixture) public static string GetPathToGlobalPackagesFolder() { - var result = ProcessHelper.RunAndCaptureOutput("dotnet", "nuget locals global-packages --list"); - var match = Regex.Match(result.output, @"^.*global-packages:\s*(.*)$"); + var (output, _) = ProcessHelper.RunAndCaptureOutput("dotnet", "nuget locals global-packages --list"); + var match = Regex.Match(output, @"^.*global-packages:\s*(.*)$"); return match.Groups[1].Value; } public static void RemovePackageFromGlobalNugetCache(string packageName) { - var pathToGlobalPackagesFolder = TestPathUtils.GetPathToGlobalPackagesFolder(); - var pathToAutoMapperPackage = Directory.GetDirectories(pathToGlobalPackagesFolder).Single(d => d.Contains(packageName, StringComparison.OrdinalIgnoreCase)); - FileUtils.RemoveDirectory(pathToAutoMapperPackage); + var pathToGlobalPackagesFolder = GetPathToGlobalPackagesFolder(); + var pathToPackage = Directory.GetDirectories(pathToGlobalPackagesFolder).SingleOrDefault(d => d.Contains(packageName, StringComparison.OrdinalIgnoreCase)); + if (pathToPackage != null) + { + FileUtils.RemoveDirectory(pathToPackage); + } } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/CachedRestorerTests.cs b/src/Dotnet.Script.Tests/CachedRestorerTests.cs index 405e5672..8e5c5566 100644 --- a/src/Dotnet.Script.Tests/CachedRestorerTests.cs +++ b/src/Dotnet.Script.Tests/CachedRestorerTests.cs @@ -2,6 +2,7 @@ using System.IO; using System.Linq; using Dotnet.Script.DependencyModel.Context; +using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.ProjectSystem; using Dotnet.Script.Shared.Tests; using Moq; @@ -22,26 +23,26 @@ public void ShouldUseCacheWhenAllPackagedArePinned() var restorerMock = new Mock(); var cachedRestorer = new CachedRestorer(restorerMock.Object, TestOutputHelper.CreateTestLogFactory()); - using (var projectFolder = new DisposableFolder()) - { + using var projectFolder = new DisposableFolder(); - var pathToProjectFile = Path.Combine(projectFolder.Path ,"script.csproj"); - var pathToCachedProjectFile = Path.Combine(projectFolder.Path ,"script.csproj.cache"); + var pathToProjectFile = Path.Combine(projectFolder.Path, "script.csproj"); + var pathToCachedProjectFile = Path.Combine(projectFolder.Path, $"script.csproj.cache"); - var projectFile = new ProjectFile(); - projectFile.PackageReferences.Add(new PackageReference("SomePackage", "1.2.3")); - projectFile.PackageReferences.Add(new PackageReference("AnotherPackage", "3.2.1")); - projectFile.Save(pathToProjectFile); + var projectFile = new ProjectFile(); + projectFile.PackageReferences.Add(new PackageReference("SomePackage", "1.2.3")); + projectFile.PackageReferences.Add(new PackageReference("AnotherPackage", "3.2.1")); + projectFile.Save(pathToProjectFile); - cachedRestorer.Restore(pathToProjectFile, NoPackageSources); - restorerMock.Verify(m => m.Restore(pathToProjectFile, NoPackageSources), Times.Once); - Assert.True(Directory.GetFiles(projectFolder.Path).Contains(pathToCachedProjectFile)); - restorerMock.Reset(); + var projectFileInfo = new ProjectFileInfo(pathToProjectFile, string.Empty); - cachedRestorer.Restore(pathToProjectFile, NoPackageSources); - restorerMock.Verify(m => m.Restore(pathToProjectFile, NoPackageSources), Times.Never); - Assert.True(Directory.GetFiles(projectFolder.Path).Contains(pathToCachedProjectFile)); - } + cachedRestorer.Restore(projectFileInfo, NoPackageSources); + restorerMock.Verify(m => m.Restore(projectFileInfo, NoPackageSources), Times.Once); + Assert.Contains(pathToCachedProjectFile, Directory.GetFiles(projectFolder.Path)); + restorerMock.Reset(); + + cachedRestorer.Restore(projectFileInfo, NoPackageSources); + restorerMock.Verify(m => m.Restore(projectFileInfo, NoPackageSources), Times.Never); + Assert.Contains(pathToCachedProjectFile, Directory.GetFiles(projectFolder.Path)); } [Fact] @@ -50,26 +51,26 @@ public void ShouldNotUseCacheWhenPackagesAreNotPinned() var restorerMock = new Mock(); var cachedRestorer = new CachedRestorer(restorerMock.Object, TestOutputHelper.CreateTestLogFactory()); - using (var projectFolder = new DisposableFolder()) - { - var projectFile = new ProjectFile(); - var pathToProjectFile = Path.Combine(projectFolder.Path ,"script.csproj"); - var pathToCachedProjectFile = Path.Combine(projectFolder.Path ,"script.csproj.cache"); + using var projectFolder = new DisposableFolder(); + var projectFile = new ProjectFile(); + var pathToProjectFile = Path.Combine(projectFolder.Path, "script.csproj"); + var pathToCachedProjectFile = Path.Combine(projectFolder.Path, $"script.csproj.cache"); + + projectFile.PackageReferences.Add(new PackageReference("SomePackage", "1.2.3")); + projectFile.PackageReferences.Add(new PackageReference("AnotherPackage", "3.2")); + projectFile.Save(pathToProjectFile); - projectFile.PackageReferences.Add(new PackageReference("SomePackage", "1.2.3")); - projectFile.PackageReferences.Add(new PackageReference("AnotherPackage", "3.2")); - projectFile.Save(pathToProjectFile); + var projectFileInfo = new ProjectFileInfo(pathToProjectFile, string.Empty); - cachedRestorer.Restore(pathToProjectFile, NoPackageSources); + cachedRestorer.Restore(projectFileInfo, NoPackageSources); - restorerMock.Verify(m => m.Restore(pathToProjectFile, NoPackageSources), Times.Once); - Assert.False(Directory.GetFiles(projectFolder.Path).Contains(pathToCachedProjectFile)); - restorerMock.Reset(); + restorerMock.Verify(m => m.Restore(projectFileInfo, NoPackageSources), Times.Once); + Assert.DoesNotContain(pathToCachedProjectFile, Directory.GetFiles(projectFolder.Path)); + restorerMock.Reset(); - cachedRestorer.Restore(pathToProjectFile, NoPackageSources); - restorerMock.Verify(m => m.Restore(pathToProjectFile, NoPackageSources), Times.Once); - Assert.False(Directory.GetFiles(projectFolder.Path).Contains(pathToCachedProjectFile)); - } + cachedRestorer.Restore(projectFileInfo, NoPackageSources); + restorerMock.Verify(m => m.Restore(projectFileInfo, NoPackageSources), Times.Once); + Assert.DoesNotContain(pathToCachedProjectFile, Directory.GetFiles(projectFolder.Path)); } [Fact] @@ -78,30 +79,29 @@ public void ShouldNotCacheWhenProjectFilesAreNotEqual() var restorerMock = new Mock(); var cachedRestorer = new CachedRestorer(restorerMock.Object, TestOutputHelper.CreateTestLogFactory()); - using (var projectFolder = new DisposableFolder()) - { - var projectFile = new ProjectFile(); - var pathToProjectFile = Path.Combine(projectFolder.Path ,"script.csproj"); - var pathToCachedProjectFile = Path.Combine(projectFolder.Path ,"script.csproj.cache"); + using var projectFolder = new DisposableFolder(); + var projectFile = new ProjectFile(); + var pathToProjectFile = Path.Combine(projectFolder.Path, "script.csproj"); + var pathToCachedProjectFile = Path.Combine(projectFolder.Path, $"script.csproj.cache"); - projectFile.PackageReferences.Add(new PackageReference("SomePackage", "1.2.3")); - projectFile.PackageReferences.Add(new PackageReference("AnotherPackage", "1.2.3")); - projectFile.Save(pathToProjectFile); + projectFile.PackageReferences.Add(new PackageReference("SomePackage", "1.2.3")); + projectFile.PackageReferences.Add(new PackageReference("AnotherPackage", "1.2.3")); + projectFile.Save(pathToProjectFile); - cachedRestorer.Restore(pathToProjectFile, NoPackageSources); + var projectFileInfo = new ProjectFileInfo(pathToProjectFile, string.Empty); - restorerMock.Verify(m => m.Restore(pathToProjectFile, NoPackageSources), Times.Once); - Assert.True(Directory.GetFiles(projectFolder.Path).Contains(pathToCachedProjectFile)); - restorerMock.Reset(); + cachedRestorer.Restore(projectFileInfo, NoPackageSources); - projectFile.PackageReferences.Add(new PackageReference("YetAnotherPackage", "1.2.3")); - projectFile.Save(pathToProjectFile); - cachedRestorer.Restore(pathToProjectFile, NoPackageSources); + restorerMock.Verify(m => m.Restore(projectFileInfo, NoPackageSources), Times.Once); + Assert.Contains(pathToCachedProjectFile, Directory.GetFiles(projectFolder.Path)); + restorerMock.Reset(); - restorerMock.Verify(m => m.Restore(pathToProjectFile, NoPackageSources), Times.Once); - Assert.True(Directory.GetFiles(projectFolder.Path).Contains(pathToCachedProjectFile)); - } - } + projectFile.PackageReferences.Add(new PackageReference("YetAnotherPackage", "1.2.3")); + projectFile.Save(pathToProjectFile); + cachedRestorer.Restore(projectFileInfo, NoPackageSources); + restorerMock.Verify(m => m.Restore(projectFileInfo, NoPackageSources), Times.Once); + Assert.Contains(pathToCachedProjectFile, Directory.GetFiles(projectFolder.Path)); + } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/Commands/ExecuteInteractiveCommandTests.cs b/src/Dotnet.Script.Tests/Commands/ExecuteInteractiveCommandTests.cs index 252b753d..9a86ea32 100644 --- a/src/Dotnet.Script.Tests/Commands/ExecuteInteractiveCommandTests.cs +++ b/src/Dotnet.Script.Tests/Commands/ExecuteInteractiveCommandTests.cs @@ -16,7 +16,7 @@ public ExecuteInteractiveCommandTests(ITestOutputHelper testOutputHelper) testOutputHelper.Capture(); } - private (ExecuteInteractiveCommand Command, ScriptConsole Console) GetExecuteInteractiveCommand(string[] commands) + private static (ExecuteInteractiveCommand Command, ScriptConsole Console) GetExecuteInteractiveCommand(string[] commands) { var reader = new StringReader(string.Join(Environment.NewLine, commands)); var writer = new StringWriter(); diff --git a/src/Dotnet.Script.Tests/CompilationDependencyResolverTests.cs b/src/Dotnet.Script.Tests/CompilationDependencyResolverTests.cs index 28fcb75b..17be547a 100644 --- a/src/Dotnet.Script.Tests/CompilationDependencyResolverTests.cs +++ b/src/Dotnet.Script.Tests/CompilationDependencyResolverTests.cs @@ -1,7 +1,9 @@ using Dotnet.Script.DependencyModel.Compilation; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.Shared.Tests; +using System; using System.IO; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -24,7 +26,7 @@ public void ShouldGetCompilationDependenciesForPackageContainingInlineNuGetPacka var resolver = CreateResolver(); var targetDirectory = TestPathUtils.GetPathToTestFixtureFolder("InlineNugetPackage"); var csxFiles = Directory.GetFiles(targetDirectory, "*.csx"); - var dependencies = resolver.GetDependencies(targetDirectory, csxFiles, true, _scriptEnvironment.TargetFramework); + var dependencies = resolver.GetDependencies(targetDirectory, csxFiles, true, _scriptEnvironment.TargetFramework); Assert.Contains(dependencies, d => d.Name == "AutoMapper"); } @@ -49,6 +51,17 @@ public void ShouldGetCompilationDependenciesForPackageContainingNativeLibrary() Assert.Contains(dependencies, d => d.Name == "Microsoft.Data.Sqlite.Core"); } + [Fact] + public void ShouldGetCompilationDependenciesForNuGetPackageWithRefFolder() + { + var resolver = CreateResolver(); + var targetDirectory = TestPathUtils.GetPathToTestFixtureFolder("InlineNugetPackageWithRefFolder"); + var csxFiles = Directory.GetFiles(targetDirectory, "*.csx"); + var dependencies = resolver.GetDependencies(targetDirectory, csxFiles, true, _scriptEnvironment.TargetFramework); + var sqlClientDependency = dependencies.Single(d => d.Name.Equals("System.Data.SqlClient", StringComparison.InvariantCultureIgnoreCase)); + Assert.Contains(sqlClientDependency.AssemblyPaths, d => d.Replace("\\", "/").Contains("system.data.sqlclient/4.6.1/ref/")); + } + [Fact] public void ShouldGetCompilationDependenciesForIssue129() { @@ -59,6 +72,16 @@ public void ShouldGetCompilationDependenciesForIssue129() Assert.Contains(dependencies, d => d.Name == "Auth0.ManagementApi"); } + [Fact] + public void ShouldGetCompilationDependenciesForWebSdk() + { + var resolver = CreateResolver(); + var targetDirectory = TestPathUtils.GetPathToTestFixtureFolder("WebApi"); + var csxFiles = Directory.GetFiles(targetDirectory, "*.csx"); + var dependencies = resolver.GetDependencies(targetDirectory, csxFiles, true, _scriptEnvironment.TargetFramework); + Assert.Contains(dependencies.SelectMany(d => d.AssemblyPaths), p => p.Contains("Microsoft.AspNetCore.Components")); + } + private CompilationDependencyResolver CreateResolver() { var resolver = new CompilationDependencyResolver(TestOutputHelper.CreateTestLogFactory()); diff --git a/src/Dotnet.Script.Tests/DisposableFolder.cs b/src/Dotnet.Script.Tests/DisposableFolder.cs index 1f15273a..1a0d6123 100644 --- a/src/Dotnet.Script.Tests/DisposableFolder.cs +++ b/src/Dotnet.Script.Tests/DisposableFolder.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using Dotnet.Script.Shared.Tests; namespace Dotnet.Script.Tests { diff --git a/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj b/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj index 0385c2a7..4b8b7da5 100644 --- a/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj +++ b/src/Dotnet.Script.Tests/Dotnet.Script.Tests.csproj @@ -1,21 +1,24 @@ + - netcoreapp2.1 + net9.0;net8.0 false + true + ../dotnet-script.snk - + runtime; build; native; contentfiles; analyzers all - - - + + + all runtime; build; native; contentfiles; analyzers - + diff --git a/src/Dotnet.Script.Tests/DotnetRestorerTests.cs b/src/Dotnet.Script.Tests/DotnetRestorerTests.cs new file mode 100644 index 00000000..cc408a23 --- /dev/null +++ b/src/Dotnet.Script.Tests/DotnetRestorerTests.cs @@ -0,0 +1,76 @@ +using Dotnet.Script.DependencyModel.Context; +using Dotnet.Script.DependencyModel.Process; +using Dotnet.Script.DependencyModel.ProjectSystem; +using Dotnet.Script.Shared.Tests; +using System; +using System.IO; +using Xunit; +using Xunit.Abstractions; + +namespace Dotnet.Script.Tests +{ + public class DotnetRestorerTests + { + private static PackageReference ValidPackageReferenceA => new PackageReference("Newtonsoft.Json", "12.0.3"); + private static PackageReference ValidPackageReferenceB => new PackageReference("Moq", "4.14.5"); + + private static PackageReference InvalidPackageReferenceA => new PackageReference("7c63e1f5-2248-ed31-9480-e4cb5ac322fe", "1.0.0"); + + public DotnetRestorerTests(ITestOutputHelper testOutputHelper) + { + testOutputHelper.Capture(); + } + + [Fact] + public void ShouldRestoreProjectPackageReferences() + { + using var projectFolder = new DisposableFolder(); + var pathToProjectFile = Path.Combine(projectFolder.Path, "script.csproj"); + + var projectFile = new ProjectFile(); + projectFile.PackageReferences.Add(ValidPackageReferenceA); + projectFile.PackageReferences.Add(ValidPackageReferenceB); + projectFile.Save(pathToProjectFile); + + var projectFileInfo = new ProjectFileInfo(pathToProjectFile, string.Empty); + + var logFactory = TestOutputHelper.CreateTestLogFactory(); + var commandRunner = new CommandRunner(logFactory); + var restorer = new DotnetRestorer(commandRunner, logFactory); + + var pathToProjectObjDirectory = Path.Combine(projectFolder.Path, "obj"); + + Assert.False(Directory.Exists(pathToProjectObjDirectory)); + + restorer.Restore(projectFileInfo, Array.Empty()); + + Assert.True(Directory.Exists(pathToProjectObjDirectory)); + } + + [Fact] + public void ShouldThrowExceptionOnRestoreError() + { + using var projectFolder = new DisposableFolder(); + var pathToProjectFile = Path.Combine(projectFolder.Path, "script.csproj"); + + var projectFile = new ProjectFile(); + projectFile.PackageReferences.Add(ValidPackageReferenceA); + projectFile.PackageReferences.Add(InvalidPackageReferenceA); + projectFile.PackageReferences.Add(ValidPackageReferenceB); + projectFile.Save(pathToProjectFile); + + var projectFileInfo = new ProjectFileInfo(pathToProjectFile, string.Empty); + + var logFactory = TestOutputHelper.CreateTestLogFactory(); + var commandRunner = new CommandRunner(logFactory); + var restorer = new DotnetRestorer(commandRunner, logFactory); + + var exception = Assert.Throws(() => + { + restorer.Restore(projectFileInfo, Array.Empty()); + }); + + Assert.Contains("NU1101", exception.Message); // unable to find package + } + } +} diff --git a/src/Dotnet.Script.Tests/EnvironmentTests.cs b/src/Dotnet.Script.Tests/EnvironmentTests.cs index 6d789e3d..b7e25930 100644 --- a/src/Dotnet.Script.Tests/EnvironmentTests.cs +++ b/src/Dotnet.Script.Tests/EnvironmentTests.cs @@ -14,24 +14,24 @@ public class EnvironmentTests [InlineData("--version")] public void ShouldPrintVersionNumber(string versionFlag) { - var result = ScriptTestRunner.Default.Execute(versionFlag); - Assert.Equal(0, result.exitCode); + var (output, exitCode) = ScriptTestRunner.Default.Execute(versionFlag); + Assert.Equal(0, exitCode); // TODO test that version appears on first line of output! // semver regex from https://github.com/semver/semver/issues/232#issue-48635632 - Assert.Matches(@"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$", result.output); + Assert.Matches(@"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$", output); } [Fact] public void ShouldPrintInfo() { - var result = ScriptTestRunner.Default.Execute("--info"); - Assert.Equal(0, result.exitCode); - Assert.Contains("Version", result.output); - Assert.Contains("Install location", result.output); - Assert.Contains("Target framework", result.output); - Assert.Contains(".NET Core version", result.output); - Assert.Contains("Platform identifier", result.output); - Assert.Contains("Runtime identifier", result.output); + var (output, exitCode) = ScriptTestRunner.Default.Execute("--info"); + Assert.Equal(0, exitCode); + Assert.Contains("Version", output); + Assert.Contains("Install location", output); + Assert.Contains("Target framework", output); + Assert.Contains(".NET Core version", output); + Assert.Contains("Platform identifier", output); + Assert.Contains("Runtime identifier", output); } } diff --git a/src/Dotnet.Script.Tests/ExecutionCacheTests.cs b/src/Dotnet.Script.Tests/ExecutionCacheTests.cs index 50779ea6..43bb5863 100644 --- a/src/Dotnet.Script.Tests/ExecutionCacheTests.cs +++ b/src/Dotnet.Script.Tests/ExecutionCacheTests.cs @@ -1,5 +1,6 @@ -using System.IO; using Dotnet.Script.Shared.Tests; +using System; +using System.IO; using Xunit; using Xunit.Abstractions; @@ -7,85 +8,136 @@ namespace Dotnet.Script.Tests { public class ExecutionCacheTests { + private readonly ITestOutputHelper testOutputHelper; + public ExecutionCacheTests(ITestOutputHelper testOutputHelper) { testOutputHelper.Capture(); + this.testOutputHelper = testOutputHelper; } [Fact] public void ShouldNotUpdateHashWhenSourceIsNotChanged() { - using (var scriptFolder = new DisposableFolder()) - { - var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); + using var scriptFolder = new DisposableFolder(); + var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); - WriteScript(pathToScript, "WriteLine(42);"); - var firstResult = Execute(pathToScript); - Assert.Contains("42", firstResult.output); + WriteScript(pathToScript, "WriteLine(42);"); + var (output, hash) = Execute(pathToScript); + Assert.Contains("42", output); + Assert.NotNull(hash); - WriteScript(pathToScript, "WriteLine(42);"); - var secondResult = Execute(pathToScript); - Assert.Contains("42", secondResult.output); + WriteScript(pathToScript, "WriteLine(42);"); + var secondResult = Execute(pathToScript); + Assert.Contains("42", secondResult.output); + Assert.NotNull(secondResult.hash); - Assert.Equal(firstResult.hash, secondResult.hash); - } + Assert.Equal(hash, secondResult.hash); } [Fact] public void ShouldUpdateHashWhenSourceChanges() { - using (var scriptFolder = new DisposableFolder()) - { - var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); + using var scriptFolder = new DisposableFolder(); + var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); - WriteScript(pathToScript, "WriteLine(42);"); - var firstResult = Execute(pathToScript); - Assert.Contains("42", firstResult.output); + WriteScript(pathToScript, "WriteLine(42);"); + var (output, hash) = Execute(pathToScript); + Assert.Contains("42", output); + Assert.NotNull(hash); - WriteScript(pathToScript, "WriteLine(84);"); - var secondResult = Execute(pathToScript); - Assert.Contains("84", secondResult.output); + WriteScript(pathToScript, "WriteLine(84);"); + var secondResult = Execute(pathToScript); + Assert.Contains("84", secondResult.output); + Assert.NotNull(secondResult.hash); - Assert.NotEqual(firstResult.hash, secondResult.hash); - } + Assert.NotEqual(hash, secondResult.hash); } [Fact] public void ShouldNotCreateHashWhenScriptIsNotCacheable() { - using (var scriptFolder = new DisposableFolder()) - { - var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); + using var scriptFolder = new DisposableFolder(); + var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); - WriteScript(pathToScript, "#r \"nuget:AutoMapper, *\"" ,"WriteLine(42);"); + WriteScript(pathToScript, "#r \"nuget:AutoMapper, *\"", "WriteLine(42);"); - var result = Execute(pathToScript); - Assert.Contains("42", result.output); + var (output, hash) = Execute(pathToScript); + Assert.Contains("42", output); - Assert.Null(result.hash); - } + Assert.Null(hash); } [Fact] public void ShouldCopyDllAndPdbToExecutionCacheFolder() { - using (var scriptFolder = new DisposableFolder()) - { - var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); + using var scriptFolder = new DisposableFolder(); + var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); + + WriteScript(pathToScript, "#r \"nuget:LightInject, 5.2.1\"", "WriteLine(42);"); + ScriptTestRunner.Default.Execute($"{pathToScript} --nocache"); + var pathToExecutionCache = GetPathToExecutionCache(pathToScript); + Assert.True(File.Exists(Path.Combine(pathToExecutionCache, "LightInject.dll"))); + Assert.True(File.Exists(Path.Combine(pathToExecutionCache, "LightInject.pdb"))); + } - WriteScript(pathToScript, "#r \"nuget:LightInject, 5.2.1\"" ,"WriteLine(42);"); - ScriptTestRunner.Default.Execute($"{pathToScript} --nocache"); - var pathToExecutionCache = GetPathToExecutionCache(pathToScript); - Assert.True(File.Exists(Path.Combine(pathToExecutionCache, "LightInject.dll"))); - Assert.True(File.Exists(Path.Combine(pathToExecutionCache, "LightInject.pdb"))); + [Fact] + public void ShouldCacheScriptsFromSameFolderIndividually() + { + static (string Output, bool Cached) Execute(string pathToScript) + { + var (output, exitCode) = ScriptTestRunner.Default.Execute($"{pathToScript} --debug"); + return (Output: output, Cached: output.Contains("Using cached compilation")); } + + using var scriptFolder = new DisposableFolder(); + var pathToScriptA = Path.Combine(scriptFolder.Path, "script.csx"); + var pathToScriptB = Path.Combine(scriptFolder.Path, "script"); + + + var idScriptA = Guid.NewGuid().ToString(); + File.AppendAllText(pathToScriptA, $@"WriteLine(""{idScriptA}"");"); + + var idScriptB = Guid.NewGuid().ToString(); + File.AppendAllText(pathToScriptB, $@"WriteLine(""{idScriptB}"");"); + + + var firstResultOfScriptA = Execute(pathToScriptA); + Assert.Contains(idScriptA, firstResultOfScriptA.Output); + Assert.False(firstResultOfScriptA.Cached); + + var firstResultOfScriptB = Execute(pathToScriptB); + Assert.Contains(idScriptB, firstResultOfScriptB.Output); + Assert.False(firstResultOfScriptB.Cached); + + + var secondResultOfScriptA = Execute(pathToScriptA); + Assert.Contains(idScriptA, secondResultOfScriptA.Output); + Assert.True(secondResultOfScriptA.Cached); + + var secondResultOfScriptB = Execute(pathToScriptB); + Assert.Contains(idScriptB, secondResultOfScriptB.Output); + Assert.True(secondResultOfScriptB.Cached); + + var idScriptB2 = Guid.NewGuid().ToString(); + File.AppendAllText(pathToScriptB, $@"WriteLine(""{idScriptB2}"");"); + + var thirdResultOfScriptA = Execute(pathToScriptA); + Assert.Contains(idScriptA, thirdResultOfScriptA.Output); + Assert.True(thirdResultOfScriptA.Cached); + + var thirdResultOfScriptB = Execute(pathToScriptB); + Assert.Contains(idScriptB, thirdResultOfScriptB.Output); + Assert.Contains(idScriptB2, thirdResultOfScriptB.Output); + Assert.False(thirdResultOfScriptB.Cached); } - private static (string output, string hash) Execute(string pathToScript) + private (string output, string hash) Execute(string pathToScript) { var result = ScriptTestRunner.Default.Execute(pathToScript); - Assert.Equal(0, result.exitCode); + testOutputHelper.WriteLine(result.Output); + Assert.Equal(0, result.ExitCode); string pathToExecutionCache = GetPathToExecutionCache(pathToScript); var pathToCacheFile = Path.Combine(pathToExecutionCache, "script.sha256"); string cachedhash = null; @@ -93,12 +145,13 @@ private static (string output, string hash) Execute(string pathToScript) { cachedhash = File.ReadAllText(pathToCacheFile); } - return (result.output, cachedhash); + + return (result.Output, cachedhash); } private static string GetPathToExecutionCache(string pathToScript) { - var pathToTempFolder = Path.GetDirectoryName(Dotnet.Script.DependencyModel.ProjectSystem.FileUtils.GetPathToTempFolder(pathToScript)); + var pathToTempFolder = Dotnet.Script.DependencyModel.ProjectSystem.FileUtils.GetPathToScriptTempFolder(pathToScript); var pathToExecutionCache = Path.Combine(pathToTempFolder, "execution-cache"); return pathToExecutionCache; } diff --git a/src/Dotnet.Script.Tests/FileUtilsTests.cs b/src/Dotnet.Script.Tests/FileUtilsTests.cs new file mode 100644 index 00000000..8a2267ab --- /dev/null +++ b/src/Dotnet.Script.Tests/FileUtilsTests.cs @@ -0,0 +1,42 @@ +using System; +using System.IO; +using Dotnet.Script.DependencyModel.ProjectSystem; +using Xunit; + +namespace Dotnet.Script.Tests +{ + public class FileUtilsTests + { + [Fact] + public void GetTempPathCanBeOverridenWithAbsolutePathViaEnvVar() + { + var path = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + try + { + Environment.SetEnvironmentVariable("DOTNET_SCRIPT_CACHE_LOCATION", path); + var tempPath = FileUtils.GetTempPath(); + Assert.Equal(path, tempPath); + } + finally + { + Environment.SetEnvironmentVariable("DOTNET_SCRIPT_CACHE_LOCATION", null); + } + } + + [Fact] + public void GetTempPathCanBeOverridenWithRelativePathViaEnvVar() + { + var path = "foo"; + try + { + Environment.SetEnvironmentVariable("DOTNET_SCRIPT_CACHE_LOCATION", path); + var tempPath = FileUtils.GetTempPath(); + Assert.Equal(Path.Combine(Directory.GetCurrentDirectory(), path), tempPath); + } + finally + { + Environment.SetEnvironmentVariable("DOTNET_SCRIPT_CACHE_LOCATION", null); + } + } + } +} diff --git a/src/Dotnet.Script.Tests/InteractiveRunnerTests.cs b/src/Dotnet.Script.Tests/InteractiveRunnerTests.cs index aa1a5ff4..da5e3b7f 100644 --- a/src/Dotnet.Script.Tests/InteractiveRunnerTests.cs +++ b/src/Dotnet.Script.Tests/InteractiveRunnerTests.cs @@ -1,6 +1,7 @@ using Xunit; using Dotnet.Script.Shared.Tests; using Xunit.Abstractions; +using System.Threading.Tasks; namespace Dotnet.Script.Tests { @@ -10,5 +11,24 @@ public class InteractiveRunnerTests : InteractiveRunnerTestsBase public InteractiveRunnerTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper) { } + + [Fact] + public async Task ShouldCompileAndExecuteWithWebSdk() + { + var commands = new[] + { + @"#r ""sdk:Microsoft.NET.Sdk.Web""", + "using Microsoft.AspNetCore.Builder;", + @"typeof(WebApplication)", + "#exit" + }; + + var ctx = GetRunner(commands); + await ctx.Runner.RunLoop(); + + var result = ctx.Console.Out.ToString(); + var error = ctx.Console.Error.ToString(); + Assert.Contains("[Microsoft.AspNetCore.Builder.WebApplication]", result); + } } } diff --git a/src/Dotnet.Script.Tests/NuGetSourceReferenceResolverTests.cs b/src/Dotnet.Script.Tests/NuGetSourceReferenceResolverTests.cs index 20ad3161..82578387 100644 --- a/src/Dotnet.Script.Tests/NuGetSourceReferenceResolverTests.cs +++ b/src/Dotnet.Script.Tests/NuGetSourceReferenceResolverTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Text; diff --git a/src/Dotnet.Script.Tests/NuGetUtilitiesTests.cs b/src/Dotnet.Script.Tests/NuGetUtilitiesTests.cs deleted file mode 100644 index e52cc640..00000000 --- a/src/Dotnet.Script.Tests/NuGetUtilitiesTests.cs +++ /dev/null @@ -1,224 +0,0 @@ -using Dotnet.Script.DependencyModel.ProjectSystem; -using NuGet.Configuration; -using System.Collections.Generic; -using System.IO; -using System.Runtime.InteropServices; -using Xunit; - -namespace Dotnet.Script.Tests -{ - using SettingsSection = Dictionary>; - using SettingsValues = Dictionary; - - public class NuGetUtilitiesTests - { - public static object[][] Args = - { - new object[] - { - @" - - - - - - - -", - new SettingsSection - { - { - "config", - new SettingsValues - { - { "dependencyVersion", "Highest" }, - { "globalPackagesFolder", @"{1}/packages" }, - { "repositoryPath", @"{0}../installed_packages" }, - { "http_proxy", @"http://company-squid:3128@contoso.com" }, - } - }, - } - }, - new object[] - { - @" - - - - -", - new SettingsSection - { - { - "bindingRedirects", - new SettingsValues - { - { "skip", "true" }, - } - }, - } - }, - new object[] - { - @" - - - - - -", - new SettingsSection - { - { - "packageRestore", - new SettingsValues - { - { "enabled", "true" }, - { "automatic", "true" }, - } - }, - } - }, - new object[] - { - @" - - - - -", - new SettingsSection - { - { - "solution", - new SettingsValues - { - { "disableSourceControlIntegration", "true" }, - } - }, - } - }, - new object[] - { - @" - - - - - - - -", - new SettingsSection - { - { - "packageSources", - new SettingsValues - { - { "nuget.org", "https://api.nuget.org/v3/index.json" }, - { "Contoso", "https://contoso.com/packages/" }, - { "Test Source", "{1}/packages" }, - { "Relative Test Source", "{0}../packages" }, - } - }, - } - }, - new object[] - { - @" - - - - -", - new SettingsSection - { - { - "apikeys", - new SettingsValues - { - { "https://MyRepo/ES/api/v2/package", "encrypted_api_key" }, - } - }, - } - }, - new object[] - { - @" - - - - -", - new SettingsSection - { - { - "disabledPackageSources", - new SettingsValues - { - { "Contoso", "true" }, - } - }, - } - }, - new object[] - { - @" - - - - -", - new SettingsSection - { - { - "activePackageSource", - new SettingsValues - { - { "nuget.org", "https://api.nuget.org/v3/index.json" }, - } - }, - } - }, - }; - - [Theory] - [MemberData(nameof(Args))] - public void ShouldGenerateEvaluatedNuGetConfigFile(string sourceNuGet, SettingsSection targetSettings) - { - using (var projectFolder = new DisposableFolder()) - { - // Generate files and directories - var sourceFolder = Path.Combine(projectFolder.Path, "Source"); - var sourceScript = Path.Combine(sourceFolder, "script.cs"); - var targetFolder = Path.Combine(projectFolder.Path, "Target"); - Directory.CreateDirectory(targetFolder); - Directory.CreateDirectory(sourceFolder); - - var rootTokens = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "c:" : string.Empty; - - var resolvedSourceNuGet = string.Format(sourceNuGet, rootTokens); - File.WriteAllText(Path.Combine(sourceFolder, Settings.DefaultSettingsFileName), resolvedSourceNuGet); - - // Evaluate and generate the NuGet config file - NuGetUtilities.CreateNuGetConfigFromLocation(sourceScript, targetFolder); - - // Validate the generated NuGet config file - var targetNuGetPath = Path.Combine(targetFolder, Settings.DefaultSettingsFileName); - Assert.True(File.Exists(targetNuGetPath), $"NuGet.config file was not generated at {targetNuGetPath}"); - - sourceFolder += Path.DirectorySeparatorChar; - var settings = new Settings(targetFolder); - foreach (var expectedSettings in targetSettings) - { - foreach (var expectedSetting in expectedSettings.Value) - { - var value = settings.GetValue(expectedSettings.Key, expectedSetting.Key); - var resolvedExpectedSetting = string.Format(expectedSetting.Value, sourceFolder, rootTokens); - Assert.Equal(resolvedExpectedSetting, value); - } - } - } - } - } -} \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/PackageSourceTests.cs b/src/Dotnet.Script.Tests/PackageSourceTests.cs index c4668213..2f02b479 100644 --- a/src/Dotnet.Script.Tests/PackageSourceTests.cs +++ b/src/Dotnet.Script.Tests/PackageSourceTests.cs @@ -17,9 +17,9 @@ public void ShouldHandleSpecifyingPackageSource() { var fixture = "ScriptPackage/WithNoNuGetConfig"; var pathToScriptPackages = ScriptPackagesFixture.GetPathToPackagesFolder(); - var result = ScriptTestRunner.Default.ExecuteFixture(fixture, $"--no-cache -s {pathToScriptPackages}"); - Assert.Contains("Hello", result.output); - Assert.Equal(0, result.exitCode); + var result = ScriptTestRunner.Default.ExecuteFixture(fixture, $"--no-cache -s \"{pathToScriptPackages}\""); + Assert.Contains("Hello", result.Output); + Assert.Equal(0, result.ExitCode); } [Fact] @@ -27,9 +27,9 @@ public void ShouldHandleSpecifyingPackageSourceWhenEvaluatingCode() { var pathToScriptPackages = ScriptPackagesFixture.GetPathToPackagesFolder(); var code = @"#load \""nuget:ScriptPackageWithMainCsx,1.0.0\"" SayHello();"; - var result = ScriptTestRunner.Default.Execute($"--no-cache -s {pathToScriptPackages} eval \"{code}\""); - Assert.Contains("Hello", result.output); - Assert.Equal(0, result.exitCode); + var result = ScriptTestRunner.Default.Execute($"--no-cache -s \"{pathToScriptPackages}\" eval \"{code}\""); + Assert.Contains("Hello", result.Output); + Assert.Equal(0, result.ExitCode); } } } diff --git a/src/Dotnet.Script.Tests/PackageVersionTests.cs b/src/Dotnet.Script.Tests/PackageVersionTests.cs index cb989473..c613444d 100644 --- a/src/Dotnet.Script.Tests/PackageVersionTests.cs +++ b/src/Dotnet.Script.Tests/PackageVersionTests.cs @@ -11,6 +11,11 @@ public class PackageVersionTests { [Theory] [InlineData("1.2.3")] + [InlineData("1.2.3.4")] + [InlineData("1.2.3-beta1")] + [InlineData("0.1.4-beta")] // See: https://github.com/dotnet-script/dotnet-script/issues/407#issuecomment-563363947 + [InlineData("2.0.0-preview3.20122.2")] // See: https://github.com/dotnet-script/dotnet-script/issues/407#issuecomment-631122591 + [InlineData("1.0.0-ci-20180920T1656")] [InlineData("[1.2]")] [InlineData("[1.2.3]")] [InlineData("[1.2.3-beta1]")] diff --git a/src/Dotnet.Script.Tests/ProjectFileTests.cs b/src/Dotnet.Script.Tests/ProjectFileTests.cs index 66b55bb4..3d8004fc 100644 --- a/src/Dotnet.Script.Tests/ProjectFileTests.cs +++ b/src/Dotnet.Script.Tests/ProjectFileTests.cs @@ -9,19 +9,17 @@ public class ProjectFileTests [Fact] public void ShouldParseProjectFile() { - using(var projectFolder = new DisposableFolder()) - { - var projectFile = new ProjectFile(); - var pathToProjectFile = Path.Combine(projectFolder.Path, "project.csproj"); - projectFile.PackageReferences.Add(new PackageReference("SomePackage","1.2.3")); - projectFile.PackageReferences.Add(new PackageReference("AnotherPackage","3.2.1")); - projectFile.Save(Path.Combine(projectFolder.Path, "project.csproj")); - - var parsedProjectFile = new ProjectFile(File.ReadAllText(pathToProjectFile)); - - Assert.Contains(new PackageReference("SomePackage", "1.2.3"), parsedProjectFile.PackageReferences); - Assert.Contains(new PackageReference("AnotherPackage", "3.2.1"), parsedProjectFile.PackageReferences); - } + using var projectFolder = new DisposableFolder(); + var projectFile = new ProjectFile(); + var pathToProjectFile = Path.Combine(projectFolder.Path, "project.csproj"); + projectFile.PackageReferences.Add(new PackageReference("SomePackage", "1.2.3")); + projectFile.PackageReferences.Add(new PackageReference("AnotherPackage", "3.2.1")); + projectFile.Save(Path.Combine(projectFolder.Path, "project.csproj")); + + var parsedProjectFile = new ProjectFile(File.ReadAllText(pathToProjectFile)); + + Assert.Contains(new PackageReference("SomePackage", "1.2.3"), parsedProjectFile.PackageReferences); + Assert.Contains(new PackageReference("AnotherPackage", "3.2.1"), parsedProjectFile.PackageReferences); } [Fact] diff --git a/src/Dotnet.Script.Tests/ScaffoldingTests.cs b/src/Dotnet.Script.Tests/ScaffoldingTests.cs index b06de5fe..8335de59 100644 --- a/src/Dotnet.Script.Tests/ScaffoldingTests.cs +++ b/src/Dotnet.Script.Tests/ScaffoldingTests.cs @@ -1,9 +1,12 @@ -using Dotnet.Script.DependencyModel.Environment; +using Dotnet.Script.Core; +using Dotnet.Script.DependencyModel.Environment; +using Dotnet.Script.Shared.Tests; using Newtonsoft.Json.Linq; +using System; using System.IO; -using System.Text.RegularExpressions; +using System.Reflection; using Xunit; - +using Xunit.Abstractions; namespace Dotnet.Script.Tests { @@ -11,171 +14,201 @@ public class ScaffoldingTests { private readonly ScriptEnvironment _scriptEnvironment; - public ScaffoldingTests() + public ScaffoldingTests(ITestOutputHelper testOutputHelper) { _scriptEnvironment = ScriptEnvironment.Default; + testOutputHelper.Capture(); } [Fact] public void ShouldInitializeScriptFolder() { - using (var scriptFolder = new DisposableFolder()) - { - var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); - Assert.Equal(0, exitCode); - Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "main.csx"))); - Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "omnisharp.json"))); - Assert.True(File.Exists(Path.Combine(scriptFolder.Path, ".vscode", "launch.json"))); - } + Assert.Equal(0, exitCode); + Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "main.csx"))); + Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "omnisharp.json"))); + Assert.True(File.Exists(Path.Combine(scriptFolder.Path, ".vscode", "launch.json"))); } - [OnlyOnUnixFact] - public void ShouldRegisterToRunCsxScriptDirectly() + [Fact] + public void ShouldInitializeScriptFolderContainingWhitespace() { - using (var scriptFolder = new DisposableFolder()) - { - var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); - Assert.True(exitCode == 0, output); + using var scriptFolder = new DisposableFolder(); + var path = Path.Combine(scriptFolder.Path, "Folder with whitespace"); + Directory.CreateDirectory(path); - var scriptPath = Path.Combine(scriptFolder.Path, "main.csx"); - var text = File.ReadAllText(scriptPath); - Assert.True(text.StartsWith("#!/usr/bin/env dotnet-script"), "should have shebang"); - Assert.True(text.IndexOf("\r\n") < 0, "should have not have windows cr/lf"); - } + var (output, exitCode) = ScriptTestRunner.Default.Execute("init", path); + + Assert.Equal(0, exitCode); + Assert.DoesNotContain("No such file or directory", output, StringComparison.OrdinalIgnoreCase); + Assert.True(File.Exists(Path.Combine(path, "main.csx"))); + Assert.True(File.Exists(Path.Combine(path, "omnisharp.json"))); + Assert.True(File.Exists(Path.Combine(path, ".vscode", "launch.json"))); } [OnlyOnUnixFact] + public void ShouldRegisterToRunCsxScriptDirectly() + { + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + Assert.True(exitCode == 0, output); + + var scriptPath = Path.Combine(scriptFolder.Path, "main.csx"); + var text = File.ReadAllText(scriptPath); + Assert.True(text.StartsWith("#!/usr/bin/env dotnet-script"), "should have shebang"); + Assert.True(text.IndexOf("\r\n") < 0, "should have not have windows cr/lf"); + } + + [OnlyOnUnixFact(Skip = "Skipping for now as it failes on Azure")] public void ShouldRunCsxScriptDirectly() { - using (var scriptFolder = new DisposableFolder()) + using var scriptFolder = new DisposableFolder(); + Directory.CreateDirectory(scriptFolder.Path); + var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + Assert.True(exitCode == 0, output); + + var scriptPath = Path.Combine(scriptFolder.Path, "main.csx"); + + // this depends on dotnet-script being installed as a dotnet global tool because the shebang needs to + // point to an executable in the environment. If you have dotnet-script installed as a global tool this + // test will pass + var (_, testExitCode) = ProcessHelper.RunAndCaptureOutput("dotnet-script", $"-h", scriptFolder.Path); + if (testExitCode == 0) { - Directory.CreateDirectory(scriptFolder.Path); - var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + (output, exitCode) = ProcessHelper.RunAndCaptureOutput(scriptPath, ""); Assert.True(exitCode == 0, output); - - var scriptPath = Path.Combine(scriptFolder.Path, "main.csx"); - - // this depends on dotnet-script being installed as a dotnet global tool because the shebang needs to - // point to an executable in the environment. If you have dotnet-script installed as a global tool this - // test will pass - var (_, testExitCode) = ProcessHelper.RunAndCaptureOutput("dotnet-script", $"-h", scriptFolder.Path); - if (testExitCode == 0) - { - (output, exitCode) = ProcessHelper.RunAndCaptureOutput(scriptPath, ""); - Assert.True(exitCode == 0, output); - Assert.Equal("Hello world!", output.Trim()); - } + Assert.Equal("Hello world!", output.Trim()); } } [Fact] public void ShouldCreateEnableScriptNugetReferencesSetting() { - using (var scriptFolder = new DisposableFolder()) - { - var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); - Assert.Equal(0, exitCode); + Assert.Equal(0, exitCode); - dynamic settings = JObject.Parse(File.ReadAllText(Path.Combine(scriptFolder.Path, "omnisharp.json"))); + dynamic settings = JObject.Parse(File.ReadAllText(Path.Combine(scriptFolder.Path, "omnisharp.json"))); - Assert.True((bool) settings.script.enableScriptNuGetReferences.Value); - } + Assert.True((bool)settings.script.enableScriptNuGetReferences.Value); } [Fact] public void ShouldCreateDefaultTargetFrameworkSetting() { - using (var scriptFolder = new DisposableFolder()) - { - var result = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("init", scriptFolder.Path); - Assert.Equal(0, result.exitCode); + Assert.Equal(0, exitCode); - dynamic settings = JObject.Parse(File.ReadAllText(Path.Combine(scriptFolder.Path, "omnisharp.json"))); + dynamic settings = JObject.Parse(File.ReadAllText(Path.Combine(scriptFolder.Path, "omnisharp.json"))); - Assert.Equal(_scriptEnvironment.TargetFramework, (string) settings.script.defaultTargetFramework.Value); - } + Assert.Equal(_scriptEnvironment.TargetFramework, (string)settings.script.defaultTargetFramework.Value); } [Fact] public void ShouldCreateNewScript() { - using (var scriptFolder = new DisposableFolder()) - { - var (output, exitCode) = ScriptTestRunner.Default.Execute("new script.csx", scriptFolder.Path); + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("new script.csx", scriptFolder.Path); - Assert.Equal(0, exitCode); - Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "script.csx"))); - } + Assert.Equal(0, exitCode); + Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "script.csx"))); } [Fact] public void ShouldCreateNewScriptWithExtension() { - using (var scriptFolder = new DisposableFolder()) - { - var (output, exitCode) = ScriptTestRunner.Default.Execute("new anotherScript", scriptFolder.Path); + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("new anotherScript", scriptFolder.Path); - Assert.Equal(0, exitCode); - Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "anotherScript.csx"))); - } + Assert.Equal(0, exitCode); + Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "anotherScript.csx"))); } [Fact] public void ShouldInitFolderWithCustomFileName() { - using (var scriptFolder = new DisposableFolder()) - { - var (output, exitCode) = ScriptTestRunner.Default.Execute("init custom.csx", scriptFolder.Path); + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("init custom.csx", scriptFolder.Path); - Assert.Equal(0, exitCode); - Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "custom.csx"))); - } + Assert.Equal(0, exitCode); + Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "custom.csx"))); } [Fact] public void ShouldInitFolderWithCustomFileNameAndExtension() { - using (var scriptFolder = new DisposableFolder()) - { - var (output, exitCode) = ScriptTestRunner.Default.Execute("init anotherCustom", scriptFolder.Path); + using var scriptFolder = new DisposableFolder(); + var (output, exitCode) = ScriptTestRunner.Default.Execute("init anotherCustom", scriptFolder.Path); - Assert.Equal(0, exitCode); - Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "anotherCustom.csx"))); - } + Assert.Equal(0, exitCode); + Assert.True(File.Exists(Path.Combine(scriptFolder.Path, "anotherCustom.csx"))); } [Fact] public void ShouldNotCreateDefaultFileForFolderWithExistingScriptFiles() { - using (var scriptFolder = new DisposableFolder()) - { - ScriptTestRunner.Default.Execute("init custom.csx", scriptFolder.Path); - ScriptTestRunner.Default.Execute("init", scriptFolder.Path); - Assert.False(File.Exists(Path.Combine(scriptFolder.Path, "main.csx"))); - } + using var scriptFolder = new DisposableFolder(); + ScriptTestRunner.Default.Execute("init custom.csx", scriptFolder.Path); + ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + Assert.False(File.Exists(Path.Combine(scriptFolder.Path, "main.csx"))); } [Fact] public void ShouldUpdatePathToDotnetScript() { - using (var scriptFolder = new DisposableFolder()) - { - ScriptTestRunner.Default.Execute("init", scriptFolder.Path); - var pathToLaunchConfiguration = Path.Combine(scriptFolder.Path, ".vscode/launch.json"); - var config = JObject.Parse(File.ReadAllText(pathToLaunchConfiguration)); + using var scriptFolder = new DisposableFolder(); + ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + var pathToLaunchConfiguration = Path.Combine(scriptFolder.Path, ".vscode/launch.json"); + var config = JObject.Parse(File.ReadAllText(pathToLaunchConfiguration)); - config.SelectToken("configurations[0].args[1]").Replace("InvalidPath/dotnet-script.dll,"); + config.SelectToken("configurations[0].args[1]").Replace("InvalidPath/dotnet-script.dll"); - File.WriteAllText(pathToLaunchConfiguration, config.ToString()); + File.WriteAllText(pathToLaunchConfiguration, config.ToString()); - ScriptTestRunner.Default.Execute("init", scriptFolder.Path); + ScriptTestRunner.Default.Execute("init", scriptFolder.Path); - config = JObject.Parse(File.ReadAllText(pathToLaunchConfiguration)); - Assert.NotEqual("InvalidPath/dotnet-script.dll", config.SelectToken("configurations[0].args[1]").Value()); - } + config = JObject.Parse(File.ReadAllText(pathToLaunchConfiguration)); + Assert.NotEqual("InvalidPath/dotnet-script.dll", config.SelectToken("configurations[0].args[1]").Value()); + } + + [Fact] + public void ShouldCreateUnifiedLaunchFileWhenInstalledAsGlobalTool() + { + Scaffolder scaffolder = CreateTestScaffolder($"somefolder{Path.DirectorySeparatorChar}.dotnet{Path.DirectorySeparatorChar}tools{Path.DirectorySeparatorChar}dotnet-script"); + + using var scriptFolder = new DisposableFolder(); + scaffolder.InitializerFolder("main.csx", scriptFolder.Path); + var fileContent = File.ReadAllText(Path.Combine(scriptFolder.Path, ".vscode", "launch.json")); + Assert.Contains("{env:HOME}/.dotnet/tools/dotnet-script", fileContent); + } + + [Fact] + public void ShouldUpdateToUnifiedLaunchFileWhenInstalledAsGlobalTool() + { + Scaffolder scaffolder = CreateTestScaffolder("some-install-folder"); + Scaffolder globalToolScaffolder = CreateTestScaffolder($"somefolder{Path.DirectorySeparatorChar}.dotnet{Path.DirectorySeparatorChar}tools{Path.DirectorySeparatorChar}dotnet-script"); + using var scriptFolder = new DisposableFolder(); + scaffolder.InitializerFolder("main.csx", scriptFolder.Path); + var fileContent = File.ReadAllText(Path.Combine(scriptFolder.Path, ".vscode", "launch.json")); + Assert.Contains("some-install-folder", fileContent); + globalToolScaffolder.InitializerFolder("main.csx", scriptFolder.Path); + fileContent = File.ReadAllText(Path.Combine(scriptFolder.Path, ".vscode", "launch.json")); + Assert.Contains("{env:HOME}/.dotnet/tools/dotnet-script", fileContent); + } + + private static Scaffolder CreateTestScaffolder(string installLocation) + { + var scriptEnvironment = (ScriptEnvironment)Activator.CreateInstance(typeof(ScriptEnvironment), nonPublic: true); + var installLocationField = typeof(ScriptEnvironment).GetField("_installLocation", BindingFlags.NonPublic | BindingFlags.Instance); + installLocationField.SetValue(scriptEnvironment, new Lazy(() => installLocation)); + var scriptConsole = new ScriptConsole(StringWriter.Null, StringReader.Null, StreamWriter.Null); + var scaffolder = new Scaffolder(TestOutputHelper.CreateTestLogFactory(), scriptConsole, scriptEnvironment); + return scaffolder; } } } diff --git a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs index 7eb1ed02..f42a4a3a 100644 --- a/src/Dotnet.Script.Tests/ScriptExecutionTests.cs +++ b/src/Dotnet.Script.Tests/ScriptExecutionTests.cs @@ -1,5 +1,7 @@ using System; using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; using System.Text; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.Shared.Tests; @@ -11,186 +13,206 @@ namespace Dotnet.Script.Tests [Collection("IntegrationTests")] public class ScriptExecutionTests { - private ScriptEnvironment _scriptEnvironment; - public ScriptExecutionTests(ITestOutputHelper testOutputHelper) { var dllCache = Path.Combine(Path.GetTempPath(), "dotnet-scripts"); FileUtils.RemoveDirectory(dllCache); testOutputHelper.Capture(); - _scriptEnvironment = ScriptEnvironment.Default; } [Fact] public void ShouldExecuteHelloWorld() { - var result = ScriptTestRunner.Default.ExecuteFixture("HelloWorld"); - Assert.Contains("Hello World", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("HelloWorld", "--no-cache"); + Assert.Contains("Hello World", output); } [Fact] public void ShouldExecuteScriptWithInlineNugetPackage() { - var result = ScriptTestRunner.Default.ExecuteFixture("InlineNugetPackage"); - Assert.Contains("AutoMapper.MapperConfiguration", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("InlineNugetPackage"); + Assert.Contains("AutoMapper.MapperConfiguration", output); + } + + [Fact] + public void ShouldHandleNullableContextAsError() + { + var (output, exitCode) = ScriptTestRunner.Default.ExecuteFixture("Nullable"); + Assert.Equal(1, exitCode); + Assert.Contains("error CS8625", output); + } + + [Fact] + public void ShouldNotHandleDisabledNullableContextAsError() + { + var (_, exitCode) = ScriptTestRunner.Default.ExecuteFixture("NullableDisabled"); + Assert.Equal(0, exitCode); } [Fact] public void ShouldIncludeExceptionLineNumberAndFile() { - var result = ScriptTestRunner.Default.ExecuteFixture("Exception", "--no-cache"); - Assert.Contains("Exception.csx:line 1", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Exception", "--no-cache"); + Assert.Contains("Exception.csx:line 1", output); } [Fact] public void ShouldHandlePackageWithNativeLibraries() { - var result = ScriptTestRunner.Default.ExecuteFixture("NativeLibrary", "--no-cache"); - Assert.Contains("Connection successful", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("NativeLibrary", "--no-cache"); + Assert.Contains("Connection successful", output); } [Fact] public void ShouldReturnExitCodeOneWhenScriptFails() { - var result = ScriptTestRunner.Default.ExecuteFixture("Exception"); - Assert.Equal(1, result.exitCode); + var (_, exitCode) = ScriptTestRunner.Default.ExecuteFixture("Exception"); + Assert.Equal(1, exitCode); } [Fact] public void ShouldReturnStackTraceInformationWhenScriptFails() { - var result = ScriptTestRunner.Default.ExecuteFixture("Exception", "--no-cache"); - Assert.Contains("die!", result.output); - Assert.Contains("Exception.csx:line 1", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Exception", "--no-cache"); + Assert.Contains("die!", output); + Assert.Contains("Exception.csx:line 1", output); } [Fact] public void ShouldReturnExitCodeOneWhenScriptFailsToCompile() { - var result = ScriptTestRunner.Default.ExecuteFixture("CompilationError"); - Assert.Equal(1, result.exitCode); + var (_, exitCode) = ScriptTestRunner.Default.ExecuteFixture("CompilationError"); + Assert.Equal(1, exitCode); + } + + [Fact] + public void ShouldWriteCompilerWarningsToStandardError() + { + var result = ScriptTestRunner.Default.ExecuteFixture(fixture: "CompilationWarning", "--no-cache"); + Assert.True(string.IsNullOrWhiteSpace(result.StandardOut)); + Assert.Contains("CS1998", result.StandardError, StringComparison.OrdinalIgnoreCase); } [Fact] public void ShouldHandleIssue129() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue129"); - Assert.Contains("Bad HTTP authentication header", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue129"); + Assert.Contains("Bad HTTP authentication header", output); } [Fact] public void ShouldHandleIssue166() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue166", "--no-cache"); - Assert.Contains("Connection successful", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue166", "--no-cache"); + Assert.Contains("Connection successful", output); } [Fact] public void ShouldPassUnknownArgumentToScript() { - var result = ScriptTestRunner.Default.ExecuteFixture("Arguments", "arg1"); - Assert.Contains("arg1", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Arguments", "arg1"); + Assert.Contains("arg1", output); } [Fact] public void ShouldPassKnownArgumentToScriptWhenEscapedByDoubleHyphen() { - var result = ScriptTestRunner.Default.ExecuteFixture("Arguments", "-- -v"); - Assert.Contains("-v", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Arguments", "-- -v"); + Assert.Contains("-v", output); } [Fact] public void ShouldNotPassUnEscapedKnownArgumentToScript() { - var result = ScriptTestRunner.Default.ExecuteFixture("Arguments", "-v"); - Assert.DoesNotContain("-v", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Arguments", "-v"); + Assert.DoesNotContain("-v", output); } [Fact] public void ShouldPropagateReturnValue() { - var result = ScriptTestRunner.Default.ExecuteFixture("ReturnValue"); - Assert.Equal(42, result.exitCode); + var (_, exitCode) = ScriptTestRunner.Default.ExecuteFixture("ReturnValue"); + Assert.Equal(42, exitCode); } [Fact] public void ShouldHandleIssue181() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue181"); - Assert.Contains("42", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue181"); + Assert.Contains("42", output); } [Fact] public void ShouldHandleIssue189() { - var result = ScriptTestRunner.Default.Execute(Path.Combine(TestPathUtils.GetPathToTestFixtureFolder("Issue189"), "SomeFolder", "Script.csx")); - Assert.Contains("Newtonsoft.Json.JsonConvert", result.output); + var (output, _) = ScriptTestRunner.Default.Execute($"\"{Path.Combine(TestPathUtils.GetPathToTestFixtureFolder("Issue189"), "SomeFolder", "Script.csx")}\""); + Assert.Contains("Newtonsoft.Json.JsonConvert", output); } [Fact] public void ShouldHandleIssue198() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue198"); - Assert.Contains("NuGet.Client", result.output); + var result = ScriptTestRunner.ExecuteFixtureInProcess("Issue198"); + // Assert.Contains("NuGet.Client", result.output); } [Fact] public void ShouldHandleIssue204() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue204"); - Assert.Contains("System.Net.WebProxy", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue204"); + Assert.Contains("System.Net.WebProxy", output); } [Fact] public void ShouldHandleIssue214() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue214"); - Assert.Contains("Hello World!", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue214"); + Assert.Contains("Hello World!", output); } [Fact] public void ShouldHandleIssue318() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue318"); - Assert.Contains("Hello World!", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue318"); + Assert.Contains("Hello World!", output); } [Theory] - [InlineData("release","false")] - [InlineData("debug","true")] + [InlineData("release", "false")] + [InlineData("debug", "true")] public void ShouldCompileScriptWithReleaseConfiguration(string configuration, string expected) { - var result = ScriptTestRunner.Default.ExecuteFixture("Configuration", $"-c {configuration}"); - Assert.Contains(expected, result.output, StringComparison.OrdinalIgnoreCase); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Configuration", $"-c {configuration}"); + Assert.Contains(expected, output, StringComparison.OrdinalIgnoreCase); } [Fact] public void ShouldCompileScriptWithDebugConfigurationWhenSpecified() { - var result = ScriptTestRunner.Default.ExecuteFixture("Configuration", "-c debug"); - Assert.Contains("true", result.output, StringComparison.OrdinalIgnoreCase); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Configuration", "-c debug"); + Assert.Contains("true", output, StringComparison.OrdinalIgnoreCase); } [Fact] public void ShouldCompileScriptWithDebugConfigurationWhenNotSpecified() { - var result = ScriptTestRunner.Default.ExecuteFixture("Configuration"); - Assert.Contains("true", result.output, StringComparison.OrdinalIgnoreCase); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Configuration"); + Assert.Contains("true", output, StringComparison.OrdinalIgnoreCase); } [Fact] public void ShouldHandleCSharp72() { - var result = ScriptTestRunner.Default.ExecuteFixture("CSharp72"); - Assert.Contains("hi", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("CSharp72"); + Assert.Contains("hi", output); } [Fact] public void ShouldHandleCSharp80() { - var result = ScriptTestRunner.Default.ExecuteFixture("CSharp80"); - Assert.Equal(0, result.exitCode); + var (_, exitCode) = ScriptTestRunner.Default.ExecuteFixture("CSharp80"); + Assert.Equal(0, exitCode); } @@ -198,78 +220,82 @@ public void ShouldHandleCSharp80() public void ShouldEvaluateCode() { var code = "Console.WriteLine(12345);"; - var result = ScriptTestRunner.Default.ExecuteCode(code); - Assert.Contains("12345", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteCode(code); + Assert.Contains("12345", output); } [Fact] public void ShouldEvaluateCodeInReleaseMode() { var code = File.ReadAllText(Path.Combine("..", "..", "..", "TestFixtures", "Configuration", "Configuration.csx")); - var result = ScriptTestRunner.Default.ExecuteCodeInReleaseMode(code); - Assert.Contains("false", result.output, StringComparison.OrdinalIgnoreCase); + var (output, _) = ScriptTestRunner.Default.ExecuteCodeInReleaseMode(code); + Assert.Contains("false", output, StringComparison.OrdinalIgnoreCase); } [Fact] public void ShouldSupportInlineNugetReferencesinEvaluatedCode() { var code = @"#r \""nuget: AutoMapper, 6.1.1\"" using AutoMapper; Console.WriteLine(typeof(MapperConfiguration));"; - var result = ScriptTestRunner.Default.ExecuteCode(code); - Assert.Contains("AutoMapper.MapperConfiguration", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteCode(code); + Assert.Contains("AutoMapper.MapperConfiguration", output); } [Fact] public void ShouldSupportInlineNugetReferencesWithTrailingSemicoloninEvaluatedCode() { var code = @"#r \""nuget: AutoMapper, 6.1.1\""; using AutoMapper; Console.WriteLine(typeof(MapperConfiguration));"; - var result = ScriptTestRunner.Default.ExecuteCode(code); - Assert.Contains("AutoMapper.MapperConfiguration", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteCode(code); + Assert.Contains("AutoMapper.MapperConfiguration", output); } - [Fact] - public void ShouldExecuteRemoteScript() + [Theory] + [InlineData("https://gist.githubusercontent.com/seesharper/5d6859509ea8364a1fdf66bbf5b7923d/raw/0a32bac2c3ea807f9379a38e251d93e39c8131cb/HelloWorld.csx", + "Hello World")] + [InlineData("http://gist.githubusercontent.com/seesharper/5d6859509ea8364a1fdf66bbf5b7923d/raw/0a32bac2c3ea807f9379a38e251d93e39c8131cb/HelloWorld.csx", + "Hello World")] + [InlineData("https://github.com/dotnet-script/dotnet-script/files/5035247/hello.csx.gz", + "Hello, world!")] + public void ShouldExecuteRemoteScript(string url, string output) { - var url = "https://gist.githubusercontent.com/seesharper/5d6859509ea8364a1fdf66bbf5b7923d/raw/0a32bac2c3ea807f9379a38e251d93e39c8131cb/HelloWorld.csx"; var result = ScriptTestRunner.Default.Execute(url); - Assert.Contains("Hello World", result.output); + Assert.Contains(output, result.Output); } [Fact] - public void ShouldExecuteRemoteScriptUsingTinyUrl() + public void ShouldHandleIssue268() { - var url = "https://tinyurl.com/y8cda9zt"; - var result = ScriptTestRunner.Default.Execute(url); - Assert.Contains("Hello World", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue268"); + Assert.Contains("value:", output); } [Fact] - public void ShouldHandleIssue268() + public void ShouldHandleIssue435() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue268"); - Assert.Contains("value:", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Issue435"); + Assert.Contains("value:Microsoft.Extensions.Configuration.ConfigurationBuilder", output); } [Fact] - public void ShouldHandleIssue435() + public void ShouldLoadMicrosoftExtensionsDependencyInjection() { - var result = ScriptTestRunner.Default.ExecuteFixture("Issue435"); - Assert.Contains("value:Microsoft.Extensions.Configuration.ConfigurationBuilder", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("MicrosoftExtensionsDependencyInjection"); + Assert.Contains("Microsoft.Extensions.DependencyInjection.IServiceCollection", output); } [Fact] public void ShouldThrowExceptionOnInvalidMediaType() { - var url = "https://github.com/filipw/dotnet-script/archive/0.20.0.zip"; - var result = ScriptTestRunner.Default.Execute(url); - Assert.Contains("not supported", result.output); + var url = "https://github.com/dotnet-script/dotnet-script/archive/0.20.0.zip"; + var (output, _) = ScriptTestRunner.Default.Execute(url); + Assert.Contains("not supported", output); } [Fact] public void ShouldHandleNonExistingRemoteScript() { var url = "https://gist.githubusercontent.com/seesharper/5d6859509ea8364a1fdf66bbf5b7923d/raw/0a32bac2c3ea807f9379a38e251d93e39c8131cb/DoesNotExists.csx"; - var result = ScriptTestRunner.Default.Execute(url); - Assert.Contains("Not Found", result.output); + var (output, _) = ScriptTestRunner.Default.Execute(url); + Assert.Contains("Not Found", output); } [Fact] @@ -278,50 +304,55 @@ public void ShouldHandleScriptUsingTheProcessClass() // This test ensures that we can load the Process class. // This used to fail when executing a netcoreapp2.0 script // from dotnet-script built for netcoreapp2.1 - var result = ScriptTestRunner.Default.ExecuteFixture("Process"); - Assert.Contains("Success", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Process"); + Assert.Contains("Success", output); } [Fact] public void ShouldHandleNuGetVersionRange() { - var result = ScriptTestRunner.Default.ExecuteFixture("VersionRange"); - Assert.Contains("AutoMapper.MapperConfiguration", result.output); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("VersionRange"); + Assert.Contains("AutoMapper.MapperConfiguration", output); } [Fact] public void ShouldThrowMeaningfulErrorMessageWhenDependencyIsNotFound() { - using(var libraryFolder = new DisposableFolder()) - { - // Create a package that we can reference + using var libraryFolder = new DisposableFolder(); + // Create a package that we can reference - ProcessHelper.RunAndCaptureOutput("dotnet", "new classlib -n SampleLibrary", libraryFolder.Path); - ProcessHelper.RunAndCaptureOutput("dotnet", "pack", libraryFolder.Path); + ProcessHelper.RunAndCaptureOutput("dotnet", "new classlib -n SampleLibrary", libraryFolder.Path); + ProcessHelper.RunAndCaptureOutput("dotnet", "pack", libraryFolder.Path); - using(var scriptFolder = new DisposableFolder()) - { - var code = new StringBuilder(); - code.AppendLine("#r \"nuget:SampleLibrary, 1.0.0\""); - code.AppendLine("WriteLine(42)"); - var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); - File.WriteAllText(pathToScript, code.ToString()); + using var scriptFolder = new DisposableFolder(); + var code = new StringBuilder(); + code.AppendLine("#r \"nuget:SampleLibrary, 1.0.0\""); + code.AppendLine("WriteLine(42)"); + var pathToScript = Path.Combine(scriptFolder.Path, "main.csx"); + File.WriteAllText(pathToScript, code.ToString()); - // Run once to ensure that it is cached. - var result = ScriptTestRunner.Default.Execute(pathToScript); - Assert.Contains("42", result.output); + // Run once to ensure that it is cached. + var result = ScriptTestRunner.Default.Execute(pathToScript); + Assert.Contains("42", result.Output); - // Remove the package from the global NuGet cache - TestPathUtils.RemovePackageFromGlobalNugetCache("SampleLibrary"); + // Remove the package from the global NuGet cache + TestPathUtils.RemovePackageFromGlobalNugetCache("SampleLibrary"); - result = ScriptTestRunner.Default.Execute(pathToScript); - Assert.Contains("Try executing/publishing the script", result.output); + //ScriptTestRunner.Default.ExecuteInProcess(pathToScript); - // Run again with the '--no-cache' option to assert that the advice actually worked. - result = ScriptTestRunner.Default.Execute($"{pathToScript} --no-cache"); - Assert.Contains("42", result.output); - } - } + result = ScriptTestRunner.Default.Execute(pathToScript); + Assert.Contains("Try executing/publishing the script", result.Output); + + // Run again with the '--no-cache' option to assert that the advice actually worked. + result = ScriptTestRunner.Default.Execute($"{pathToScript} --no-cache"); + Assert.Contains("42", result.Output); + } + + [Fact] + public void ShouldHandleIssue613() + { + var (_, exitCode) = ScriptTestRunner.Default.ExecuteFixture("Issue613"); + Assert.Equal(0, exitCode); } [Fact] @@ -353,19 +384,169 @@ public TestClass() Console.WriteLine(""Hello World!"");"; - using (var disposableFolder = new DisposableFolder()) - { - var projectFolder = Path.Combine(disposableFolder.Path, "TestLibrary"); - ProcessHelper.RunAndCaptureOutput("dotnet", "new classlib -n TestLibrary", disposableFolder.Path); - ProcessHelper.RunAndCaptureOutput("dotnet", "add TestLibrary.csproj package AgileObjects.AgileMapper -v 0.25.0", projectFolder); - File.WriteAllText(Path.Combine(projectFolder, "Class1.cs"), code); - File.WriteAllText(Path.Combine(projectFolder, "script.csx"), script); - ProcessHelper.RunAndCaptureOutput("dotnet", "build -c release -o testlib", projectFolder); + using var disposableFolder = new DisposableFolder(); + var projectFolder = Path.Combine(disposableFolder.Path, "TestLibrary"); + ProcessHelper.RunAndCaptureOutput("dotnet", "new classlib -n TestLibrary", disposableFolder.Path); + ProcessHelper.RunAndCaptureOutput("dotnet", "add TestLibrary.csproj package AgileObjects.AgileMapper -v 0.25.0", projectFolder); + File.WriteAllText(Path.Combine(projectFolder, "Class1.cs"), code); + File.WriteAllText(Path.Combine(projectFolder, "script.csx"), script); + ProcessHelper.RunAndCaptureOutput("dotnet", "build -c release -o testlib", projectFolder); - var result = ScriptTestRunner.Default.Execute(Path.Combine(projectFolder, "script.csx")); + var (output, exitCode) = ScriptTestRunner.Default.Execute(Path.Combine(projectFolder, "script.csx")); - Assert.Contains("Hello World!", result.output); - } + Assert.Contains("Hello World!", output); + } + + + [Fact] + public void ShouldHandleLocalNuGetConfigWithRelativePath() + { + TestPathUtils.RemovePackageFromGlobalNugetCache("NuGetConfigTestLibrary"); + + using var packageLibraryFolder = new DisposableFolder(); + CreateTestPackage(packageLibraryFolder.Path); + + string pathToScriptFile = CreateTestScript(packageLibraryFolder.Path); + + var (output, exitCode) = ScriptTestRunner.Default.Execute(pathToScriptFile); + Assert.Contains("Success", output); + } + + [Fact] + public void ShouldHandleLocalNuGetConfigWithRelativePathInParentFolder() + { + TestPathUtils.RemovePackageFromGlobalNugetCache("NuGetConfigTestLibrary"); + + using var packageLibraryFolder = new DisposableFolder(); + CreateTestPackage(packageLibraryFolder.Path); + + var scriptFolder = Path.Combine(packageLibraryFolder.Path, "ScriptFolder"); + Directory.CreateDirectory(scriptFolder); + string pathToScriptFile = CreateTestScript(scriptFolder); + + var (output, exitCode) = ScriptTestRunner.Default.Execute(pathToScriptFile); + Assert.Contains("Success", output); + } + + [Fact] + public void ShouldHandleLocalNuGetFileWhenPathContainsSpace() + { + TestPathUtils.RemovePackageFromGlobalNugetCache("NuGetConfigTestLibrary"); + + using var packageLibraryFolder = new DisposableFolder(); + var packageLibraryFolderPath = Path.Combine(packageLibraryFolder.Path, "library folder"); + Directory.CreateDirectory(packageLibraryFolderPath); + + CreateTestPackage(packageLibraryFolderPath); + + string pathToScriptFile = CreateTestScript(packageLibraryFolderPath); + + var (output, exitCode) = ScriptTestRunner.Default.Execute($"\"{pathToScriptFile}\""); + Assert.Contains("Success", output); + } + + [Fact] + public void ShouldHandleScriptWithTargetFrameworkInShebang() + { + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("TargetFrameworkInShebang"); + Assert.Contains("Hello world!", output); + } + + [Fact] + public void ShouldIgnoreGlobalJsonInScriptFolder() + { + var fixture = "InvalidGlobalJson"; + var workingDirectory = Path.GetDirectoryName(TestPathUtils.GetPathToTestFixture(fixture)); + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("InvalidGlobalJson", $"--no-cache", workingDirectory); + Assert.Contains("Hello world!", output); + } + + [Fact] + public void ShouldIsolateScriptAssemblies() + { + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("Isolation", "--isolated-load-context"); + Assert.Contains("2.0.0.0", output); + } + + [Fact] + public void ShouldSetCurrentContextualReflectionContext() + { + var (output, _) = ScriptTestRunner.Default.ExecuteFixture("CurrentContextualReflectionContext", "--isolated-load-context"); + Assert.Contains("Dotnet.Script.Core.ScriptAssemblyLoadContext", output); + } + +#if NET6_0 + [Fact] + public void ShouldCompileAndExecuteWithWebSdk() + { + var processResult = ScriptTestRunner.Default.ExecuteFixture("WebApiNet6", "--no-cache"); + Assert.Equal(0, processResult.ExitCode); + } +#endif + +#if NET7_0 + [Fact] + public void ShouldCompileAndExecuteWithWebSdk() + { + var processResult = ScriptTestRunner.Default.ExecuteFixture("WebApi", "--no-cache"); + Assert.Equal(0, processResult.ExitCode); + } +#endif + +#if NET8_0 + // .NET 8.0 only works with isolated load context + [Fact] + public void ShouldCompileAndExecuteWithWebSdk() + { + var processResult = ScriptTestRunner.Default.ExecuteFixture("WebApi", "--no-cache"); + Assert.Equal(0, processResult.ExitCode); + } +#endif + + [Fact] + public void ShouldThrowExceptionWhenSdkIsNotSupported() + { + var processResult = ScriptTestRunner.Default.ExecuteFixture("UnsupportedSdk", "--no-cache"); + Assert.StartsWith("The sdk 'Unsupported' is not supported", processResult.StandardError); + } + + [Fact] + public void ShouldRespectEnvironmentExitCodeOfTheScript() + { + var processResult = ScriptTestRunner.Default.ExecuteFixture("EnvironmentExitCode", "--no-cache"); + Assert.Equal(0xA0, processResult.ExitCode); + } + + private static string CreateTestScript(string scriptFolder) + { + string script = @" +#r ""nuget:NuGetConfigTestLibrary, 1.0.0"" +WriteLine(""Success""); + "; + string pathToScriptFile = Path.Combine(scriptFolder, "testscript.csx"); + File.WriteAllText(pathToScriptFile, script); + return pathToScriptFile; + } + + private static void CreateTestPackage(string packageLibraryFolder) + { + ProcessHelper.RunAndCaptureOutput("dotnet", "new classlib -n NuGetConfigTestLibrary -f netstandard2.0", packageLibraryFolder); + var projectFolder = Path.Combine(packageLibraryFolder, "NuGetConfigTestLibrary"); + ProcessHelper.RunAndCaptureOutput("dotnet", $"pack -o \"{Path.Combine(packageLibraryFolder, "packagePath")}\"", projectFolder); + CreateNuGetConfig(packageLibraryFolder); + } + + private static void CreateNuGetConfig(string packageLibraryFolder) + { + string nugetConfig = @" + + + + + +> + "; + File.WriteAllText(Path.Combine(packageLibraryFolder, "NuGet.Config"), nugetConfig); } } } diff --git a/src/Dotnet.Script.Tests/ScriptFilesResolverTests.cs b/src/Dotnet.Script.Tests/ScriptFilesResolverTests.cs index 94187ac2..4d47e02b 100644 --- a/src/Dotnet.Script.Tests/ScriptFilesResolverTests.cs +++ b/src/Dotnet.Script.Tests/ScriptFilesResolverTests.cs @@ -10,88 +10,97 @@ public class ScriptFilesResolverTests [Fact] public void ShouldOnlyResolveRootScript() { - using (var rootFolder = new DisposableFolder()) - { - var rootScript = WriteScript(string.Empty, rootFolder.Path, "Foo.csx"); - WriteScript(string.Empty, rootFolder.Path, "Bar.csx"); - var scriptFilesResolver = new ScriptFilesResolver(); - - var files = scriptFilesResolver.GetScriptFiles(rootScript); - - Assert.True(files.Count == 1); - Assert.Contains(files, f => f.Contains("Foo.csx")); - Assert.Contains(files, f => !f.Contains("Bar.csx")); - } + using var rootFolder = new DisposableFolder(); + var rootScript = WriteScript(string.Empty, rootFolder.Path, "Foo.csx"); + WriteScript(string.Empty, rootFolder.Path, "Bar.csx"); + var scriptFilesResolver = new ScriptFilesResolver(); + + var files = scriptFilesResolver.GetScriptFiles(rootScript); + + Assert.True(files.Count == 1); + Assert.Contains(files, f => f.Contains("Foo.csx")); + Assert.Contains(files, f => !f.Contains("Bar.csx")); } - [Fact] - public void ShouldResolveLoadedScriptInRootFolder() + + [Theory] + [InlineData("#load \"Bar.csx\"")] + // See: https://github.com/dotnet-script/dotnet-script/issues/720 (1) + [InlineData("#load \"Bar.csx\"\n\n\"Hello world\".Dump();")] + public void ShouldResolveLoadedScriptInRootFolder(string rootScriptContent) { - using (var rootFolder = new DisposableFolder()) - { - var rootScript = WriteScript("#load \"Bar.csx\"", rootFolder.Path, "Foo.csx"); - WriteScript(string.Empty, rootFolder.Path, "Bar.csx"); - var scriptFilesResolver = new ScriptFilesResolver(); - - var files = scriptFilesResolver.GetScriptFiles(rootScript); - - Assert.True(files.Count == 2); - Assert.Contains(files, f => f.Contains("Foo.csx")); - Assert.Contains(files, f => f.Contains("Bar.csx")); - } + using var rootFolder = new DisposableFolder(); + var rootScript = WriteScript(rootScriptContent, rootFolder.Path, "Foo.csx"); + WriteScript(string.Empty, rootFolder.Path, "Bar.csx"); + var scriptFilesResolver = new ScriptFilesResolver(); + + var files = scriptFilesResolver.GetScriptFiles(rootScript); + + Assert.True(files.Count == 2); + Assert.Contains(files, f => f.Contains("Foo.csx")); + Assert.Contains(files, f => f.Contains("Bar.csx")); } [Fact] public void ShouldResolveLoadedScriptInSubFolder() { - using (var rootFolder = new DisposableFolder()) - { - var rootScript = WriteScript("#load \"SubFolder/Bar.csx\"", rootFolder.Path, "Foo.csx"); - var subFolder = Path.Combine(rootFolder.Path, "SubFolder"); - Directory.CreateDirectory(subFolder); - WriteScript(string.Empty, subFolder, "Bar.csx"); - - var scriptFilesResolver = new ScriptFilesResolver(); - var files = scriptFilesResolver.GetScriptFiles(rootScript); - - Assert.True(files.Count == 2); - Assert.Contains(files, f => f.Contains("Foo.csx")); - Assert.Contains(files, f => f.Contains("Bar.csx")); - } + using var rootFolder = new DisposableFolder(); + var rootScript = WriteScript("#load \"SubFolder/Bar.csx\"", rootFolder.Path, "Foo.csx"); + var subFolder = Path.Combine(rootFolder.Path, "SubFolder"); + Directory.CreateDirectory(subFolder); + WriteScript(string.Empty, subFolder, "Bar.csx"); + + var scriptFilesResolver = new ScriptFilesResolver(); + var files = scriptFilesResolver.GetScriptFiles(rootScript); + + Assert.True(files.Count == 2); + Assert.Contains(files, f => f.Contains("Foo.csx")); + Assert.Contains(files, f => f.Contains("Bar.csx")); } [Fact] public void ShouldResolveLoadedScriptWithRootPath() { - using (var rootFolder = new DisposableFolder()) - { - var subFolder = Path.Combine(rootFolder.Path, "SubFolder"); - Directory.CreateDirectory(subFolder); - var fullPathToBarScript = WriteScript(string.Empty, subFolder, "Bar.csx"); - var rootScript = WriteScript($"#load \"{fullPathToBarScript}\"", rootFolder.Path, "Foo.csx"); + using var rootFolder = new DisposableFolder(); + var subFolder = Path.Combine(rootFolder.Path, "SubFolder"); + Directory.CreateDirectory(subFolder); + var fullPathToBarScript = WriteScript(string.Empty, subFolder, "Bar.csx"); + var rootScript = WriteScript($"#load \"{fullPathToBarScript}\"", rootFolder.Path, "Foo.csx"); - var scriptFilesResolver = new ScriptFilesResolver(); - var files = scriptFilesResolver.GetScriptFiles(rootScript); + var scriptFilesResolver = new ScriptFilesResolver(); + var files = scriptFilesResolver.GetScriptFiles(rootScript); - Assert.True(files.Count == 2); - Assert.Contains(files, f => f.Contains("Foo.csx")); - Assert.Contains(files, f => f.Contains("Bar.csx")); - } + Assert.True(files.Count == 2); + Assert.Contains(files, f => f.Contains("Foo.csx")); + Assert.Contains(files, f => f.Contains("Bar.csx")); } [Fact] public void ShouldNotParseLoadDirectiveIgnoringCase() { - using (var rootFolder = new DisposableFolder()) - { - var rootScript = WriteScript("#LOAD \"Bar.csx\"", rootFolder.Path, "Foo.csx"); - WriteScript(string.Empty, rootFolder.Path, "Bar.csx"); - var scriptFilesResolver = new ScriptFilesResolver(); + using var rootFolder = new DisposableFolder(); + var rootScript = WriteScript("#LOAD \"Bar.csx\"", rootFolder.Path, "Foo.csx"); + WriteScript(string.Empty, rootFolder.Path, "Bar.csx"); + var scriptFilesResolver = new ScriptFilesResolver(); + + var files = scriptFilesResolver.GetScriptFiles(rootScript); + + Assert.Equal(new[] { "Foo.csx" }, files.Select(Path.GetFileName)); + } + + [Fact] + public void ShouldParseFilesStartingWithNuGet() + { + using var rootFolder = new DisposableFolder(); + var rootScript = WriteScript("#load \"NuGet.csx\"", rootFolder.Path, "Foo.csx"); + WriteScript(string.Empty, rootFolder.Path, "NuGet.csx"); + var scriptFilesResolver = new ScriptFilesResolver(); - var files = scriptFilesResolver.GetScriptFiles(rootScript); + var files = scriptFilesResolver.GetScriptFiles(rootScript); - Assert.Equal(new[] { "Foo.csx" }, files.Select(Path.GetFileName)); - } + Assert.True(files.Count == 2); + Assert.Contains(files, f => f.Contains("Foo.csx")); + Assert.Contains(files, f => f.Contains("NuGet.csx")); } private static string WriteScript(string content, string folder, string name) diff --git a/src/Dotnet.Script.Tests/ScriptPackagesFixture.cs b/src/Dotnet.Script.Tests/ScriptPackagesFixture.cs index 1a1350e6..51870f10 100644 --- a/src/Dotnet.Script.Tests/ScriptPackagesFixture.cs +++ b/src/Dotnet.Script.Tests/ScriptPackagesFixture.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text.RegularExpressions; using Dotnet.Script.DependencyModel.Environment; +using Dotnet.Script.Shared.Tests; namespace Dotnet.Script.Tests { @@ -34,7 +35,7 @@ private void BuildScriptPackages() RemoveDirectory(pathToPackagesOutputFolder); Directory.CreateDirectory(pathToPackagesOutputFolder); var specFiles = GetSpecFiles(); - var baseDirectory = AppDomain.CurrentDomain.BaseDirectory; + _ = AppDomain.CurrentDomain.BaseDirectory; var pathtoNuget430 = Path.Combine("../../../NuGet/NuGet430.exe"); foreach (var specFile in specFiles) { @@ -42,12 +43,12 @@ private void BuildScriptPackages() if (_scriptEnvironment.IsWindows) { command = pathtoNuget430; - var result = ProcessHelper.RunAndCaptureOutput(command, $"pack {specFile} -OutputDirectory {pathToPackagesOutputFolder}" ); + _ = ProcessHelper.RunAndCaptureOutput(command, $"pack \"{specFile}\" -OutputDirectory \"{pathToPackagesOutputFolder}\""); } else { command = "mono"; - var result = ProcessHelper.RunAndCaptureOutput(command, $"{pathtoNuget430} pack {specFile} -OutputDirectory {pathToPackagesOutputFolder}"); + _ = ProcessHelper.RunAndCaptureOutput(command, $"\"{pathtoNuget430}\" pack \"{specFile}\" -OutputDirectory \"{pathToPackagesOutputFolder}\""); } } diff --git a/src/Dotnet.Script.Tests/ScriptPackagesTests.cs b/src/Dotnet.Script.Tests/ScriptPackagesTests.cs index 774b4297..81e61618 100644 --- a/src/Dotnet.Script.Tests/ScriptPackagesTests.cs +++ b/src/Dotnet.Script.Tests/ScriptPackagesTests.cs @@ -2,8 +2,7 @@ using System.IO; using System.Linq; using System.Text; -using Dotnet.Script.DependencyModel.Compilation; -using Dotnet.Script.DependencyModel.Environment; +using Dotnet.Script.DependencyModel.Runtime; using Dotnet.Script.Shared.Tests; using Xunit; using Xunit.Abstractions; @@ -13,92 +12,93 @@ namespace Dotnet.Script.Tests [Collection("IntegrationTests")] public class ScriptPackagesTests : IClassFixture { - private readonly ScriptEnvironment _scriptEnvironment; - public ScriptPackagesTests(ITestOutputHelper testOutputHelper) { testOutputHelper.Capture(); - _scriptEnvironment = ScriptEnvironment.Default; } [Fact] public void ShouldHandleScriptPackageWithMainCsx() { - var result = Execute("WithMainCsx/WithMainCsx.csx"); - Assert.StartsWith("Hello from netstandard2.0", result); + var (output, exitcode) = ScriptTestRunner.Default.ExecuteWithScriptPackage("WithMainCsx", "--no-cache"); + Assert.Equal(0, exitcode); + Assert.StartsWith("Hello from netstandard2.0", output); } - [Fact] + [Fact(Skip = "Needs review.")] public void ShouldThrowMeaningfulExceptionWhenScriptPackageIsMissing() { - using(var scriptFolder = new DisposableFolder()) - { - var code = new StringBuilder(); - code.AppendLine("#load \"nuget:ScriptPackageWithMainCsx, 1.0.0\""); - code.AppendLine("SayHello();"); - var pathToScriptFile = Path.Combine(scriptFolder.Path, "main.csx"); - File.WriteAllText(pathToScriptFile, code.ToString()); - var pathToScriptPackages = Path.GetFullPath(ScriptPackagesFixture.GetPathToPackagesFolder()); - - // Run once to ensure that it is cached. - var result = ScriptTestRunner.Default.Execute($"{pathToScriptFile} -s {pathToScriptPackages}"); - Assert.StartsWith("Hello from netstandard2.0", result.output); - - // Remove the package from the global NuGet cache - TestPathUtils.RemovePackageFromGlobalNugetCache("ScriptPackageWithMainCsx"); - - //Change the source to force a recompile, now with the missing package. - code.Append("return 0;"); - File.WriteAllText(pathToScriptFile, code.ToString()); - - result = ScriptTestRunner.Default.Execute($"{pathToScriptFile} -s {pathToScriptPackages}"); - Assert.Contains("Try executing/publishing the script", result.output); - } + using var scriptFolder = new DisposableFolder(); + var code = new StringBuilder(); + code.AppendLine("#load \"nuget:ScriptPackageWithMainCsx, 1.0.0\""); + code.AppendLine("SayHello();"); + var pathToScriptFile = Path.Combine(scriptFolder.Path, "main.csx"); + File.WriteAllText(pathToScriptFile, code.ToString()); + var pathToScriptPackages = Path.GetFullPath(ScriptPackagesFixture.GetPathToPackagesFolder()); + + // Run once to ensure that it is cached. + var result = ScriptTestRunner.Default.Execute($"{pathToScriptFile} -s {pathToScriptPackages}"); + Assert.StartsWith("Hello from netstandard2.0", result.Output); + + // Remove the package from the global NuGet cache + TestPathUtils.RemovePackageFromGlobalNugetCache("ScriptPackageWithMainCsx"); + + //Change the source to force a recompile, now with the missing package. + code.Append("return 0;"); + File.WriteAllText(pathToScriptFile, code.ToString()); + + result = ScriptTestRunner.Default.Execute($"{pathToScriptFile} -s {pathToScriptPackages}"); + Assert.Contains("Try executing/publishing the script", result.Output); } [Fact] public void ShouldHandleScriptWithAnyTargetFramework() { - var result = Execute("WithAnyTargetFramework/WithAnyTargetFramework.csx"); - Assert.StartsWith("Hello from any target framework", result); + var (output, exitcode) = ScriptTestRunner.Default.ExecuteWithScriptPackage("WithAnyTargetFramework", "--no-cache"); + Assert.Equal(0, exitcode); + Assert.StartsWith("Hello from any target framework", output); } [Fact] public void ShouldHandleScriptPackageWithNoEntryPointFile() { - var result = Execute("WithNoEntryPointFile/WithNoEntryPointFile.csx"); - Assert.Contains("Hello from Foo.csx", result); - Assert.Contains("Hello from Bar.csx", result); + var (output, exitcode) = ScriptTestRunner.Default.ExecuteWithScriptPackage("WithNoEntryPointFile", "--no-cache"); + Assert.Equal(0, exitcode); + Assert.Contains("Hello from Foo.csx", output); + Assert.Contains("Hello from Bar.csx", output); } [Fact] public void ShouldHandleScriptPackageWithScriptPackageDependency() { - var result = Execute("WithScriptPackageDependency/WithScriptPackageDependency.csx"); - Assert.StartsWith("Hello from netstandard2.0", result); + var (output, exitcode) = ScriptTestRunner.Default.ExecuteWithScriptPackage("WithScriptPackageDependency", "--no-cache"); + Assert.Equal(0, exitcode); + Assert.StartsWith("Hello from netstandard2.0", output); } [Fact] public void ShouldThrowExceptionWhenReferencingUnknownPackage() { - var result = Execute("WithInvalidPackageReference/WithInvalidPackageReference.csx"); - Assert.StartsWith("Unable to restore packages from", result); + var (output, exitcode) = ScriptTestRunner.Default.ExecuteWithScriptPackage("WithInvalidPackageReference", "--no-cache"); + Assert.NotEqual(0, exitcode); + Assert.StartsWith("Unable to restore packages from", output); } [Fact] public void ShouldHandleScriptPackageWithSubFolder() { - var result = Execute("WithSubFolder/WithSubFolder.csx"); - Assert.StartsWith("Hello from Bar.csx", result); + var (output, exitcode) = ScriptTestRunner.Default.ExecuteWithScriptPackage("WithSubFolder", "--no-cache"); + Assert.Equal(0, exitcode); + Assert.StartsWith("Hello from Bar.csx", output); } [Fact] public void ShouldGetScriptFilesFromScriptPackage() { - var resolver = CreateCompilationDependencyResolver(); + var resolver = CreateRuntimeDependencyResolver(); var fixture = GetFullPathToTestFixture("ScriptPackage/WithMainCsx"); var csxFiles = Directory.GetFiles(fixture, "*.csx"); - var dependencies = resolver.GetDependencies(fixture, csxFiles, true, _scriptEnvironment.TargetFramework); + var dependencies = resolver.GetDependencies(csxFiles.First(), Array.Empty()); var scriptFiles = dependencies.Single(d => d.Name == "ScriptPackageWithMainCsx").Scripts; Assert.NotEmpty(scriptFiles); } @@ -110,9 +110,9 @@ private static string GetFullPathToTestFixture(string path) } - private CompilationDependencyResolver CreateCompilationDependencyResolver() + private RuntimeDependencyResolver CreateRuntimeDependencyResolver() { - var resolver = new CompilationDependencyResolver(TestOutputHelper.CreateTestLogFactory()); + var resolver = new RuntimeDependencyResolver(TestOutputHelper.CreateTestLogFactory(), useRestoreCache: false); return resolver; } @@ -128,7 +128,7 @@ private string Execute(string scriptFileName) Console.SetError(stringWriter); var baseDir = AppDomain.CurrentDomain.BaseDirectory; var fullPathToScriptFile = Path.Combine(baseDir, "..", "..", "..", "TestFixtures", "ScriptPackage", scriptFileName); - Program.Main(new[] { fullPathToScriptFile , "--no-cache"}); + Program.Main(new[] { fullPathToScriptFile, "--no-cache" }); return output.ToString(); } diff --git a/src/Dotnet.Script.Tests/ScriptParserTests.cs b/src/Dotnet.Script.Tests/ScriptParserTests.cs index 77372ea9..2c361d3e 100644 --- a/src/Dotnet.Script.Tests/ScriptParserTests.cs +++ b/src/Dotnet.Script.Tests/ScriptParserTests.cs @@ -14,7 +14,7 @@ public class ScriptParserTests public ScriptParserTests(ITestOutputHelper testOutputHelper) { - testOutputHelper.Capture(minimumLogLevel:0); + testOutputHelper.Capture(minimumLogLevel: 0); _scriptEnvironment = ScriptEnvironment.Default; } @@ -70,7 +70,7 @@ public void ShouldResolveUniquePackages(string id, string version) var result = parser.ParseFromCode(code.ToString()); Assert.Equal(1, result.PackageReferences.Count); - Assert.Equal("Package", result.PackageReferences.Single().Id.Value ); + Assert.Equal("Package", result.PackageReferences.Single().Id.Value); Assert.Equal("1.2.3-beta-1", result.PackageReferences.Single().Version.Value); } @@ -123,30 +123,6 @@ public void ShouldNotMatchBadDirectives(string code) Assert.Equal(0, result.PackageReferences.Count); } - [Fact] - public void ShouldParseTargetFramework() - { - var parser = CreateParser(); - - var result = parser.ParseFromCode($"#! \"{_scriptEnvironment.TargetFramework}\""); - - Assert.Equal(_scriptEnvironment.TargetFramework, result.TargetFramework); - } - - [Theory] - [InlineData("\n#! \"TARGET_FRAMEWORK\"")] - [InlineData("\r#! \"TARGET_FRAMEWORK\"")] - [InlineData("#!\n\"TARGET_FRAMEWORK\"")] - [InlineData("#!\r\"TARGET_FRAMEWORK\"")] - public void ShouldNotParseBadTargetFramework(string code) - { - var parser = CreateParser(); - - var result = parser.ParseFromCode(code.Replace("TARGET_FRAMEWORK", _scriptEnvironment.TargetFramework)); - - Assert.Null(result.TargetFramework); - } - private ScriptParser CreateParser() { return new ScriptParser(TestOutputHelper.CreateTestLogFactory()); diff --git a/src/Dotnet.Script.Tests/ScriptProjectProviderTests.cs b/src/Dotnet.Script.Tests/ScriptProjectProviderTests.cs index ee52347b..fdc3bc03 100644 --- a/src/Dotnet.Script.Tests/ScriptProjectProviderTests.cs +++ b/src/Dotnet.Script.Tests/ScriptProjectProviderTests.cs @@ -1,5 +1,7 @@ using System.IO; +using System.Linq; using System.Text; +using System.Xml.Linq; using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.ProjectSystem; using Dotnet.Script.Shared.Tests; @@ -10,7 +12,7 @@ namespace Dotnet.Script.Tests { [Collection("IntegrationTests")] public class ScriptProjectProviderTests - { + { private readonly ScriptEnvironment _scriptEnvironment; public ScriptProjectProviderTests(ITestOutputHelper testOutputHelper) @@ -19,31 +21,36 @@ public ScriptProjectProviderTests(ITestOutputHelper testOutputHelper) _scriptEnvironment = ScriptEnvironment.Default; } - [Fact] - public void ShouldCopyLocalNuGetConfig() - { - var provider = CreateProvider(); - var pathToProjectFile = provider.CreateProject(TestPathUtils.GetPathToTestFixtureFolder("LocalNuGetConfig"), _scriptEnvironment.TargetFramework, true); - var pathToProjectFileFolder = Path.GetDirectoryName(pathToProjectFile); - Assert.True(File.Exists(Path.Combine(pathToProjectFileFolder,"NuGet.Config"))); - } - [Fact] public void ShouldLogProjectFileContent() { - StringBuilder log = new StringBuilder(); + StringBuilder log = new StringBuilder(); var provider = new ScriptProjectProvider(type => ((level, message, exception) => log.AppendLine(message))); provider.CreateProject(TestPathUtils.GetPathToTestFixtureFolder("HelloWorld"), _scriptEnvironment.TargetFramework, true); var output = log.ToString(); - Assert.Contains("",output); + Assert.Contains("", output); } - private ScriptProjectProvider CreateProvider() + [Fact] + public void ShouldUseSpecifiedSdk() { - ScriptProjectProvider provider = new ScriptProjectProvider(TestOutputHelper.CreateTestLogFactory()); - return provider; + var provider = new ScriptProjectProvider(TestOutputHelper.CreateTestLogFactory()); + var projectFileInfo = provider.CreateProject(TestPathUtils.GetPathToTestFixtureFolder("WebApi"), _scriptEnvironment.TargetFramework, true); + Assert.Equal("Microsoft.NET.Sdk.Web", XDocument.Load(projectFileInfo.Path).Descendants("Project").Single().Attributes("Sdk").Single().Value); + } + + // See: https://github.com/dotnet-script/dotnet-script/issues/723 + [Theory] + [InlineData("#!/usr/bin/env dotnet-script\n#r \"sdk:Microsoft.NET.Sdk.Web\"")] + [InlineData("#!/usr/bin/env dotnet-script\n\n#r \"sdk:Microsoft.NET.Sdk.Web\"")] + public void ShouldHandleShebangBeforeSdk(string code) + { + var parser = new ScriptParser(TestOutputHelper.CreateTestLogFactory()); + var result = parser.ParseFromCode(code); + + Assert.Equal("Microsoft.NET.Sdk.Web", result.Sdk); } } } \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/ScriptPublisherTests.cs b/src/Dotnet.Script.Tests/ScriptPublisherTests.cs index 8bfb230b..80da0361 100644 --- a/src/Dotnet.Script.Tests/ScriptPublisherTests.cs +++ b/src/Dotnet.Script.Tests/ScriptPublisherTests.cs @@ -1,10 +1,10 @@ -using Dotnet.Script.Core; -using Dotnet.Script.DependencyModel.Environment; +using Dotnet.Script.DependencyModel.Environment; using Dotnet.Script.DependencyModel.Logging; using Dotnet.Script.DependencyModel.Process; using Dotnet.Script.Shared.Tests; using System; using System.IO; +using System.Linq; using Xunit; using Xunit.Abstractions; @@ -25,210 +25,231 @@ public ScriptPublisherTests(ITestOutputHelper testOutputHelper) [Fact] public void SimplePublishTest() { - using (var workspaceFolder = new DisposableFolder()) - { - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); + using var workspaceFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute($"publish {mainPath}", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish {mainPath}", workspaceFolder.Path); + Assert.Equal(0, exitCode); - var exePath = Path.Combine(workspaceFolder.Path, "publish", _scriptEnvironment.RuntimeIdentifier, "script"); - var executableRunResult = _commandRunner.Execute(exePath); + var exePath = Path.Combine(workspaceFolder.Path, "publish", _scriptEnvironment.RuntimeIdentifier, "main"); + var executableRunResult = _commandRunner.Execute(exePath); - Assert.Equal(0, executableRunResult); - } + Assert.Equal(0, executableRunResult); + + var publishedFiles = Directory.EnumerateFiles(Path.Combine(workspaceFolder.Path, "publish", _scriptEnvironment.RuntimeIdentifier)); + if (_scriptEnvironment.NetCoreVersion.Major >= 3) + Assert.True(publishedFiles.Count() == 1, "There should be only a single published file"); + else + Assert.True(publishedFiles.Count() > 1, "There should be multiple published files"); } [Fact] public void SimplePublishTestToDifferentRuntimeId() { - using (var workspaceFolder = new DisposableFolder()) - { - var runtimeId = _scriptEnvironment.RuntimeIdentifier == "win10-x64" ? "osx-x64" : "win10-x64"; - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute($"publish {mainPath} --runtime {runtimeId}"); - - Assert.Equal(0, publishResult.exitCode); - - var publishPath = Path.Combine(workspaceFolder.Path, "publish", runtimeId); - Assert.True(Directory.Exists(publishPath), $"Publish directory {publishPath} was not found."); - - using (var enumerator = Directory.EnumerateFiles(publishPath).GetEnumerator()) - { - Assert.True(enumerator.MoveNext(), $"Publish directory {publishPath} was empty."); - } - } + using var workspaceFolder = new DisposableFolder(); +#if NET8_0 + var runtimeId = _scriptEnvironment.RuntimeIdentifier == "win-x64" ? "osx-x64" : "win10-x64"; +#else + var runtimeId = _scriptEnvironment.RuntimeIdentifier == "win10-x64" ? "osx-x64" : "win10-x64"; +#endif + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish {mainPath} --runtime {runtimeId}"); + + Assert.Equal(0, exitCode); + + var publishPath = Path.Combine(workspaceFolder.Path, "publish", runtimeId); + Assert.True(Directory.Exists(publishPath), $"Publish directory {publishPath} was not found."); + + using var enumerator = Directory.EnumerateFiles(publishPath).GetEnumerator(); + Assert.True(enumerator.MoveNext(), $"Publish directory {publishPath} was empty."); } [Fact] public void SimplePublishToOtherFolderTest() { - using (var workspaceFolder = new DisposableFolder()) - using (var publishRootFolder = new DisposableFolder()) - { - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute($"publish {mainPath} -o {publishRootFolder.Path}"); - Assert.Equal(0, publishResult.exitCode); - - var exePath = Path.Combine(publishRootFolder.Path, "script"); - var executableRunResult = _commandRunner.Execute(exePath); - - Assert.Equal(0, executableRunResult); - } + using var workspaceFolder = new DisposableFolder(); + using var publishRootFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish {mainPath} -o {publishRootFolder.Path}"); + Assert.Equal(0, exitCode); + + var exePath = Path.Combine(publishRootFolder.Path, "main"); + var executableRunResult = _commandRunner.Execute(exePath); + + Assert.Equal(0, executableRunResult); } [Fact] public void SimplePublishFromCurrentDirectoryTest() { - using (var workspaceFolder = new DisposableFolder()) - { - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute("publish main.csx", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); - - var exePath = Path.Combine(workspaceFolder.Path, "publish", _scriptEnvironment.RuntimeIdentifier, "script"); - var executableRunResult = _commandRunner.Execute(exePath); - - Assert.Equal(0, executableRunResult); - } + using var workspaceFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute("publish main.csx", workspaceFolder.Path); + Assert.Equal(0, exitCode); + + var exePath = Path.Combine(workspaceFolder.Path, "publish", _scriptEnvironment.RuntimeIdentifier, "main"); + var executableRunResult = _commandRunner.Execute(exePath); + + Assert.Equal(0, executableRunResult); } [Fact] public void SimplePublishFromCurrentDirectoryToOtherFolderTest() { - using (var workspaceFolder = new DisposableFolder()) - { - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute("publish main.csx -o publish", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); - - var exePath = Path.Combine(workspaceFolder.Path, "publish", "script"); - var executableRunResult = _commandRunner.Execute(exePath); - - Assert.Equal(0, executableRunResult); - } + using var workspaceFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute("publish main.csx -o publish", workspaceFolder.Path); + Assert.Equal(0, exitCode); + + var exePath = Path.Combine(workspaceFolder.Path, "publish", "main"); + var executableRunResult = _commandRunner.Execute(exePath); + + Assert.Equal(0, executableRunResult); } [Fact] public void SimplePublishDllTest() { - using (var workspaceFolder = new DisposableFolder()) - { - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute($"publish {mainPath} --dll", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); - - var dllPath = Path.Combine("publish", "main.dll"); - var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", workspaceFolder.Path); - - Assert.Equal(0, dllRunResult.exitCode); - } + using var workspaceFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish {mainPath} --dll", workspaceFolder.Path); + Assert.Equal(0, exitCode); + + var dllPath = Path.Combine("publish", "main.dll"); + var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", workspaceFolder.Path); + + Assert.Equal(0, dllRunResult.ExitCode); } [Fact] public void SimplePublishDllFromCurrentDirectoryTest() { - using (var workspaceFolder = new DisposableFolder()) - { - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute("publish main.csx --dll", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); + using var workspaceFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute("publish main.csx --dll", workspaceFolder.Path); + Assert.Equal(0, exitCode); - var dllPath = Path.Combine(workspaceFolder.Path, "publish", "main.dll"); + var dllPath = Path.Combine(workspaceFolder.Path, "publish", "main.dll"); - var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", workspaceFolder.Path); + var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", workspaceFolder.Path); - Assert.Equal(0, dllRunResult.exitCode); - } + Assert.Equal(0, dllRunResult.ExitCode); } [Fact] public void SimplePublishDllToOtherFolderTest() { - using (var workspaceFolder = new DisposableFolder()) - using (var publishFolder = new DisposableFolder()) - { - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute($"publish {mainPath} --dll -o {publishFolder.Path}", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); - - var dllPath = Path.Combine(publishFolder.Path, "main.dll"); - var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", publishFolder.Path); - - Assert.Equal(0, dllRunResult.exitCode); - } + using var workspaceFolder = new DisposableFolder(); + using var publishFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish {mainPath} --dll -o {publishFolder.Path}", workspaceFolder.Path); + Assert.Equal(0, exitCode); + + var dllPath = Path.Combine(publishFolder.Path, "main.dll"); + var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", publishFolder.Path); + + Assert.Equal(0, dllRunResult.ExitCode); } [Fact] public void CustomDllNameTest() { - using (var workspaceFolder = new DisposableFolder()) - { - var outputName = "testName"; - var assemblyName = $"{outputName}.dll"; - var code = @"WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute($"publish main.csx --dll -n {outputName}", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); - - var dllPath = Path.Combine(workspaceFolder.Path, "publish", assemblyName); - var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", workspaceFolder.Path); - - Assert.Equal(0, dllRunResult.exitCode); - } + using var workspaceFolder = new DisposableFolder(); + var outputName = "testName"; + var assemblyName = $"{outputName}.dll"; + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish main.csx --dll -n {outputName}", workspaceFolder.Path); + Assert.Equal(0, exitCode); + + var dllPath = Path.Combine(workspaceFolder.Path, "publish", assemblyName); + var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath}", workspaceFolder.Path); + + Assert.Equal(0, dllRunResult.ExitCode); + } + + [Fact] + public void CustomExeNameTest() + { + using var workspaceFolder = new DisposableFolder(); + var exeName = "testName"; + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish main.csx -o publish -n {exeName}", workspaceFolder.Path); + Assert.Equal(0, exitCode); + + var exePath = Path.Combine(workspaceFolder.Path, "publish", exeName); + var executableRunResult = _commandRunner.Execute(exePath); + + Assert.Equal(0, executableRunResult); } [Fact] public void DllWithArgsTests() { - using (var workspaceFolder = new DisposableFolder()) - { - var code = @"WriteLine(""Hello "" + Args[0] + Args[1] + Args[2] + Args[3] + Args[4]);"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - var publishResult = ScriptTestRunner.Default.Execute($"publish {mainPath} --dll", workspaceFolder.Path); - Assert.Equal(0, publishResult.exitCode); - - var dllPath = Path.Combine(workspaceFolder.Path, "publish", "main.dll"); - var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath} -- w o r l d", workspaceFolder.Path); - - Assert.Equal(0, dllRunResult.exitCode); - Assert.Contains("Hello world", dllRunResult.output); - } + using var workspaceFolder = new DisposableFolder(); + var code = @"WriteLine(""Hello "" + Args[0] + Args[1] + Args[2] + Args[3] + Args[4]);"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + var (output, exitCode) = ScriptTestRunner.Default.Execute($"publish {mainPath} --dll", workspaceFolder.Path); + Assert.Equal(0, exitCode); + + var dllPath = Path.Combine(workspaceFolder.Path, "publish", "main.dll"); + var dllRunResult = ScriptTestRunner.Default.Execute($"exec {dllPath} -- w o r l d", workspaceFolder.Path); + + Assert.Equal(0, dllRunResult.ExitCode); + Assert.Contains("Hello world", dllRunResult.Output); } [Fact] public void ShouldHandleReferencingAssemblyFromScriptFolder() { - using (var workspaceFolder = new DisposableFolder()) - { - ProcessHelper.RunAndCaptureOutput($"dotnet",$" new classlib -n MyCustomLibrary -o {workspaceFolder.Path}"); - ProcessHelper.RunAndCaptureOutput($"dotnet", $" build -o {workspaceFolder.Path}", workspaceFolder.Path); - var code = $@"#r ""MyCustomLibrary.dll"" {Environment.NewLine} WriteLine(""hello world"");"; - var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); - File.WriteAllText(mainPath, code); - - var publishResult = ScriptTestRunner.Default.Execute("publish main.csx --dll --output .", workspaceFolder.Path); - - Assert.Equal(0, publishResult.exitCode); - } + using var workspaceFolder = new DisposableFolder(); + ProcessHelper.RunAndCaptureOutput($"dotnet", $" new classlib -n MyCustomLibrary -o {workspaceFolder.Path}"); + ProcessHelper.RunAndCaptureOutput($"dotnet", $" build -o {workspaceFolder.Path}", workspaceFolder.Path); + var code = $@"#r ""MyCustomLibrary.dll"" {Environment.NewLine} WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + + var (output, exitCode) = ScriptTestRunner.Default.Execute("publish main.csx --dll --output .", workspaceFolder.Path); + + Assert.Equal(0, exitCode); + } + + [Fact] + public void ShouldHandleSpaceInPublishFolder() + { + using var workspaceFolder = new DisposableFolder(); + var code = @"WriteLine(""hello world"");"; + var mainPath = Path.Combine(workspaceFolder.Path, "main.csx"); + File.WriteAllText(mainPath, code); + + var (output, exitCode) = ScriptTestRunner.Default.Execute(@"publish main.csx -o ""publish folder""", workspaceFolder.Path); + + Assert.Equal(0, exitCode); + + var exePath = Path.Combine(workspaceFolder.Path, "publish folder", "main"); + var executableRunResult = _commandRunner.Execute(exePath); + + Assert.Equal(0, executableRunResult); } private LogFactory GetLogFactory() diff --git a/src/Dotnet.Script.Tests/ScriptRunnerTests.cs b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs new file mode 100644 index 00000000..e9bc7de3 --- /dev/null +++ b/src/Dotnet.Script.Tests/ScriptRunnerTests.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Dotnet.Script.Core; +using Dotnet.Script.DependencyModel.Runtime; +using Dotnet.Script.Shared.Tests; +using Gapotchenko.FX.Reflection; +using Moq; +using Xunit; + +namespace Dotnet.Script.Tests +{ + public class ScriptRunnerTests + { + [Fact] + public void ResolveAssembly_ReturnsNull_WhenRuntimeDepsMapDoesNotContainAssembly() + { + var scriptRunner = CreateScriptRunner(); + + var result = scriptRunner.ResolveAssembly(AssemblyLoadPal.ForCurrentAppDomain, new AssemblyName("AnyAssemblyName"), new Dictionary()); + + Assert.Null(result); + } + + private static ScriptRunner CreateScriptRunner() + { + var logFactory = TestOutputHelper.CreateTestLogFactory(); + var scriptCompiler = new ScriptCompiler(logFactory, false); + + return new ScriptRunner(scriptCompiler, logFactory, ScriptConsole.Default); + } + } +} \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/ScriptTestRunner.cs b/src/Dotnet.Script.Tests/ScriptTestRunner.cs index deeabb58..02db0fe5 100644 --- a/src/Dotnet.Script.Tests/ScriptTestRunner.cs +++ b/src/Dotnet.Script.Tests/ScriptTestRunner.cs @@ -11,7 +11,7 @@ public class ScriptTestRunner { public static readonly ScriptTestRunner Default = new ScriptTestRunner(); - private ScriptEnvironment _scriptEnvironment; + private readonly ScriptEnvironment _scriptEnvironment; static ScriptTestRunner() { @@ -21,30 +21,37 @@ static ScriptTestRunner() private ScriptTestRunner() { - _scriptEnvironment = ScriptEnvironment.Default; + _scriptEnvironment = ScriptEnvironment.Default; } - - public (string output, int exitCode) Execute(string arguments, string workingDirectory = null) + + public ProcessResult Execute(string arguments, string workingDirectory = null) { var result = ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments(arguments), workingDirectory); return result; } public int ExecuteInProcess(string arguments = null) - { + { return Program.Main(arguments?.Split(" ") ?? Array.Empty()); } - public (string output, int exitCode) ExecuteFixture(string fixture, string arguments = null) + public ProcessResult ExecuteFixture(string fixture, string arguments = null, string workingDirectory = null) { var pathToFixture = TestPathUtils.GetPathToTestFixture(fixture); - var result = ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"{pathToFixture} {arguments}")); + var result = ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"\"{pathToFixture}\" {arguments}"), workingDirectory); return result; } - public int ExecuteFixtureInProcess(string fixture, string arguments = null) + public ProcessResult ExecuteWithScriptPackage(string fixture, string arguments = null, string workingDirectory = null) + { + var pathToScriptPackageFixtures = TestPathUtils.GetPathToTestFixtureFolder("ScriptPackage"); + var pathToFixture = Path.Combine(pathToScriptPackageFixtures, fixture, $"{fixture}.csx"); + return ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"\"{pathToFixture}\" {arguments}"), workingDirectory); + } + + public static int ExecuteFixtureInProcess(string fixture, string arguments = null) { - var pathToFixture = TestPathUtils.GetPathToTestFixture(fixture); + var pathToFixture = TestPathUtils.GetPathToTestFixture(fixture); return Program.Main(new[] { pathToFixture }.Concat(arguments?.Split(" ") ?? Array.Empty()).ToArray()); } @@ -60,14 +67,14 @@ public static int ExecuteCodeInProcess(string code, string arguments) return Program.Main(allArguments.ToArray()); } - public (string output, int exitCode) ExecuteCode(string code) + public ProcessResult ExecuteCode(string code) { var result = ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"eval \"{code}\"")); return result; } - public (string output, int exitCode) ExecuteCodeInReleaseMode(string code) - { + public ProcessResult ExecuteCodeInReleaseMode(string code) + { var result = ProcessHelper.RunAndCaptureOutput("dotnet", GetDotnetScriptArguments($"-c release eval \"{code}\"")); return result; } @@ -80,7 +87,8 @@ private string GetDotnetScriptArguments(string arguments) #else configuration = "Release"; #endif - var allArgs = $"exec {Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "Dotnet.Script", "bin", configuration, _scriptEnvironment.TargetFramework, "dotnet-script.dll")} {arguments}"; + + var allArgs = $"exec \"{Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "Dotnet.Script", "bin", configuration, _scriptEnvironment.TargetFramework, "dotnet-script.dll")}\" {arguments}"; return allArgs; } diff --git a/src/Dotnet.Script.Tests/ScriptdependencyContextReaderTests.cs b/src/Dotnet.Script.Tests/ScriptdependencyContextReaderTests.cs index 4746a0b9..401fe96f 100644 --- a/src/Dotnet.Script.Tests/ScriptdependencyContextReaderTests.cs +++ b/src/Dotnet.Script.Tests/ScriptdependencyContextReaderTests.cs @@ -18,16 +18,14 @@ public ScriptDependencyContextReaderTests(ITestOutputHelper testOutputHelper) [Fact] public void ShouldThrowMeaningfulExceptionWhenRuntimeTargetIsMissing() { - using (var projectFolder = new DisposableFolder()) - { - ProcessHelper.RunAndCaptureOutput("dotnet", "new console -n SampleLibrary", projectFolder.Path); - ProcessHelper.RunAndCaptureOutput("dotnet", "restore", projectFolder.Path); - var pathToAssetsFile = Path.Combine(projectFolder.Path, "SampleLibrary" ,"obj","project.assets.json"); - var dependencyResolver = new ScriptDependencyContextReader(TestOutputHelper.CreateTestLogFactory()); + using var projectFolder = new DisposableFolder(); + ProcessHelper.RunAndCaptureOutput("dotnet", "new console -n SampleLibrary", projectFolder.Path); + ProcessHelper.RunAndCaptureOutput("dotnet", "restore", projectFolder.Path); + var pathToAssetsFile = Path.Combine(projectFolder.Path, "SampleLibrary", "obj", "project.assets.json"); + var dependencyResolver = new ScriptDependencyContextReader(TestOutputHelper.CreateTestLogFactory()); - var exception = Assert.Throws(() => dependencyResolver.ReadDependencyContext(pathToAssetsFile)); - Assert.Contains("Make sure that the project file was restored using a RID (runtime identifier).", exception.Message); - } + var exception = Assert.Throws(() => dependencyResolver.ReadDependencyContext(pathToAssetsFile)); + Assert.Contains("Make sure that the project file was restored using a RID (runtime identifier).", exception.Message); } [Fact] diff --git a/src/Dotnet.Script.Tests/TestFixtures/CompilationWarning/CompilationWarning.csx b/src/Dotnet.Script.Tests/TestFixtures/CompilationWarning/CompilationWarning.csx new file mode 100644 index 00000000..883f543a --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/CompilationWarning/CompilationWarning.csx @@ -0,0 +1,3 @@ +public async Task Foo() +{ +} \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/CurrentContextualReflectionContext/CurrentContextualReflectionContext.csx b/src/Dotnet.Script.Tests/TestFixtures/CurrentContextualReflectionContext/CurrentContextualReflectionContext.csx new file mode 100644 index 00000000..e7e2420e --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/CurrentContextualReflectionContext/CurrentContextualReflectionContext.csx @@ -0,0 +1,4 @@ +using System.Runtime.Loader; + +var context = AssemblyLoadContext.CurrentContextualReflectionContext; +Console.WriteLine(context?.ToString() ?? ""); diff --git a/src/Dotnet.Script.Tests/TestFixtures/EnvironmentExitCode/EnvironmentExitCode.csx b/src/Dotnet.Script.Tests/TestFixtures/EnvironmentExitCode/EnvironmentExitCode.csx new file mode 100644 index 00000000..ab8f26d5 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/EnvironmentExitCode/EnvironmentExitCode.csx @@ -0,0 +1,2 @@ +Environment.ExitCode = 0xA0; +Console.WriteLine("Hello World"); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/InlineNugetPackage/InlineNugetPackage.csx b/src/Dotnet.Script.Tests/TestFixtures/InlineNugetPackage/InlineNugetPackage.csx index 4bc147e3..08ca5f09 100644 --- a/src/Dotnet.Script.Tests/TestFixtures/InlineNugetPackage/InlineNugetPackage.csx +++ b/src/Dotnet.Script.Tests/TestFixtures/InlineNugetPackage/InlineNugetPackage.csx @@ -1,4 +1,12 @@ #r "nuget:AutoMapper, 6.1.1" using AutoMapper; -Console.WriteLine(typeof(MapperConfiguration)); \ No newline at end of file + +Console.WriteLine(typeof(MapperConfiguration)); + +void SayHi(string hi) +{ + Console.WriteLine(hi); +} + +SayHi(null); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/InlineNugetPackageWithRefFolder/InlineNugetPackageWithRefFolder.csx b/src/Dotnet.Script.Tests/TestFixtures/InlineNugetPackageWithRefFolder/InlineNugetPackageWithRefFolder.csx new file mode 100644 index 00000000..df7ee99a --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/InlineNugetPackageWithRefFolder/InlineNugetPackageWithRefFolder.csx @@ -0,0 +1,5 @@ +#r "nuget:System.Data.SqlClient, 4.6.1" + +using System.Data.SqlClient; + +Console.WriteLine(typeof(SqlConnection)); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/InvalidGlobalJson/InvalidGlobalJson.csx b/src/Dotnet.Script.Tests/TestFixtures/InvalidGlobalJson/InvalidGlobalJson.csx new file mode 100644 index 00000000..8c7603a8 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/InvalidGlobalJson/InvalidGlobalJson.csx @@ -0,0 +1 @@ +Console.WriteLine("Hello world!"); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/InvalidGlobalJson/global.json b/src/Dotnet.Script.Tests/TestFixtures/InvalidGlobalJson/global.json new file mode 100644 index 00000000..79422f0c --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/InvalidGlobalJson/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "3.0.100" + } +} diff --git a/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx b/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx new file mode 100644 index 00000000..8d2d2427 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/Isolation/Isolation.csx @@ -0,0 +1,3 @@ +#r "nuget: Microsoft.Extensions.Logging.Console, 2.0.0" +using Microsoft.Extensions.Logging; +Console.WriteLine(typeof(ConsoleLoggerExtensions).Assembly.GetName().Version); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/Issue613/Issue613.csx b/src/Dotnet.Script.Tests/TestFixtures/Issue613/Issue613.csx new file mode 100644 index 00000000..bbe48cd4 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/Issue613/Issue613.csx @@ -0,0 +1,18 @@ +#r "nuget: System.Text.Encoding.CodePages, 5.0.0" + +public class Script +{ + public static void Run() + { + try + { + System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); + } + catch (Exception ex) + { + System.Console.WriteLine(ex.ToString()); + } + } +} + +Script.Run(); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/LocalNuGetConfig/NuGet.Config b/src/Dotnet.Script.Tests/TestFixtures/LocalNuGetConfig/NuGet.Config index 368b3ea5..5e150b6e 100644 --- a/src/Dotnet.Script.Tests/TestFixtures/LocalNuGetConfig/NuGet.Config +++ b/src/Dotnet.Script.Tests/TestFixtures/LocalNuGetConfig/NuGet.Config @@ -1,6 +1,7 @@  + \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/MicrosoftExtensionsDependencyInjection/MicrosoftExtensionsDependencyInjection.csx b/src/Dotnet.Script.Tests/TestFixtures/MicrosoftExtensionsDependencyInjection/MicrosoftExtensionsDependencyInjection.csx new file mode 100644 index 00000000..9c964377 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/MicrosoftExtensionsDependencyInjection/MicrosoftExtensionsDependencyInjection.csx @@ -0,0 +1,4 @@ +#!/usr/bin/env dotnet-script +#r "nuget:Microsoft.Extensions.DependencyInjection, 3.0.1" +using Microsoft.Extensions.DependencyInjection; +Console.WriteLine(typeof(IServiceCollection)); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/Nullable/Nullable.csx b/src/Dotnet.Script.Tests/TestFixtures/Nullable/Nullable.csx new file mode 100644 index 00000000..8239f084 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/Nullable/Nullable.csx @@ -0,0 +1,4 @@ +#nullable enable + +string x = null; +Console.WriteLine(x); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/NullableDisabled/NullableDisabled.csx b/src/Dotnet.Script.Tests/TestFixtures/NullableDisabled/NullableDisabled.csx new file mode 100644 index 00000000..776de1c0 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/NullableDisabled/NullableDisabled.csx @@ -0,0 +1,2 @@ +string x = null; +Console.WriteLine(x); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/TargetFrameworkInShebang/TargetFrameworkInShebang.csx b/src/Dotnet.Script.Tests/TestFixtures/TargetFrameworkInShebang/TargetFrameworkInShebang.csx new file mode 100644 index 00000000..68beff41 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/TargetFrameworkInShebang/TargetFrameworkInShebang.csx @@ -0,0 +1,2 @@ +#!"netcoreapp2.0" +Console.WriteLine("Hello world!"); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/UnsupportedSdk/UnsupportedSdk.csx b/src/Dotnet.Script.Tests/TestFixtures/UnsupportedSdk/UnsupportedSdk.csx new file mode 100644 index 00000000..4f30c5e3 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/UnsupportedSdk/UnsupportedSdk.csx @@ -0,0 +1 @@ +#r "sdk:Unsupported" \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/WebApi/WebApi.csx b/src/Dotnet.Script.Tests/TestFixtures/WebApi/WebApi.csx new file mode 100644 index 00000000..ff851bad --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/WebApi/WebApi.csx @@ -0,0 +1,8 @@ +#r "sdk:Microsoft.NET.Sdk.Web" + +using Microsoft.AspNetCore.Builder; + +var builder = WebApplication.CreateBuilder(); +var app = builder.Build(); + +app.MapGet("/", () => "Hello World!"); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/TestFixtures/WebApiNet6/WebApiNet6.csx b/src/Dotnet.Script.Tests/TestFixtures/WebApiNet6/WebApiNet6.csx new file mode 100644 index 00000000..b4b76d41 --- /dev/null +++ b/src/Dotnet.Script.Tests/TestFixtures/WebApiNet6/WebApiNet6.csx @@ -0,0 +1,6 @@ +#r "sdk:Microsoft.NET.Sdk.Web" + +using Microsoft.AspNetCore.Builder; + +var a = WebApplication.Create(); +a.MapGet("/", () => "Hello world"); \ No newline at end of file diff --git a/src/Dotnet.Script.Tests/VersioningTests.cs b/src/Dotnet.Script.Tests/VersioningTests.cs index 6313547a..346ed3ca 100644 --- a/src/Dotnet.Script.Tests/VersioningTests.cs +++ b/src/Dotnet.Script.Tests/VersioningTests.cs @@ -50,7 +50,7 @@ public async Task ShouldNotReportLatestVersionWhenAlreayRunningLatest() Assert.DoesNotContain("Version Y is now available", output.ToString()); } - private async Task ReportWith(string currentVersion, string latestVersion) + private static async Task ReportWith(string currentVersion, string latestVersion) { var versionProviderMock = new Mock(); versionProviderMock.Setup(m => m.GetCurrentVersion()).Returns(new VersionInfo(currentVersion, true)); @@ -71,10 +71,10 @@ private async Task ReportWith(string currentVersion, string latestVersio public async Task ShouldLogErrorMessageWhenResolvingLatestVersionFails() { StringWriter log = new StringWriter(); - LogFactory logFactory = (type) => (level,message,exception) => - { - log.WriteLine(message); - }; + Logger logFactory(Type type) => (level, message, exception) => + { + log.WriteLine(message); + }; var versionProviderMock = new Mock(); versionProviderMock.Setup(m => m.GetCurrentVersion()).Returns(new VersionInfo("X", true)); diff --git a/src/Dotnet.Script/Dotnet.Script.csproj b/src/Dotnet.Script/Dotnet.Script.csproj index efdc6e16..e33e4619 100644 --- a/src/Dotnet.Script/Dotnet.Script.csproj +++ b/src/Dotnet.Script/Dotnet.Script.csproj @@ -1,40 +1,44 @@ - - - Dotnet CLI tool allowing you to run C# (CSX) scripts. - 0.29.0 - filipw - Dotnet.Script - netcoreapp2.1 - portable - dotnet-script - Exe - dotnet;cli;script;csx;csharp;roslyn - https://raw.githubusercontent.com/filipw/Strathweb.TypedRouting.AspNetCore/master/strathweb.png - https://github.com/filipw/dotnet-script - MIT - git - https://github.com/filipw/dotnet-script.git - false - false - false - false - true - latest - - - - - - - - - - - - Always - - - Always - - - + + + + Dotnet CLI tool allowing you to run C# (CSX) scripts. + 1.6.0 + filipw + Dotnet.Script + net9.0;net8.0 + portable + dotnet-script + Exe + dotnet;cli;script;csx;csharp;roslyn + https://avatars.githubusercontent.com/u/113979420 + https://github.com/dotnet-script/dotnet-script + MIT + git + https://github.com/dotnet-script/dotnet-script.git + false + false + false + false + true + latest + true + ../dotnet-script.snk + + + + + + + + + + + + + Always + + + Always + + + \ No newline at end of file diff --git a/src/Dotnet.Script/LogHelper.cs b/src/Dotnet.Script/LogHelper.cs index f22cc84e..dd5de434 100644 --- a/src/Dotnet.Script/LogHelper.cs +++ b/src/Dotnet.Script/LogHelper.cs @@ -1,128 +1,54 @@ using System; -using System.Collections.Concurrent; -using System.Reflection; -using System.Runtime.InteropServices; using Dotnet.Script.DependencyModel.Logging; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Console; -using Microsoft.Extensions.Logging.Console.Internal; +using Microsoft.Extensions.Options; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace Dotnet.Script { public static class LogHelper - { + { public static LogFactory CreateLogFactory(string verbosity) { var logLevel = (LogLevel)LevelMapper.FromString(verbosity); - - var loggerFactory = new LoggerFactory(); - loggerFactory.AddProvider(new ConsoleErrorLoggerProvider((message, level) => level >= logLevel)); + var loggerFilterOptions = new LoggerFilterOptions() { MinLevel = logLevel }; + + var consoleLoggerProvider = new ConsoleLoggerProvider(new ConsoleOptionsMonitor()); + + var loggerFactory = new LoggerFactory(new[] { consoleLoggerProvider }, loggerFilterOptions); return type => { var logger = loggerFactory.CreateLogger(type); return (level, message, exception) => { - logger.Log((LogLevel)level, message, exception); + logger.Log((LogLevel)level, message, exception); }; }; - } - } - - public class WindowsLogErrorConsole : IConsole - { - private void SetColor(ConsoleColor? background, ConsoleColor? foreground) - { - if (background.HasValue) - { - Console.BackgroundColor = background.Value; - } - - if (foreground.HasValue) - { - Console.ForegroundColor = foreground.Value; - } - } - - private void ResetColor() - { - Console.ResetColor(); - } - - public void Write(string message, ConsoleColor? background, ConsoleColor? foreground) - { - SetColor(background, foreground); - Console.Error.Write(message); - ResetColor(); - } - - public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground) - { - SetColor(background, foreground); - Console.Error.WriteLine(message); - ResetColor(); - } - - public void Flush() - { - // No action required as for every write, data is sent directly to the console - // output stream } } - public class AnsiSystemErrorConsole : IAnsiSystemConsole + internal class ConsoleOptionsMonitor : IOptionsMonitor { - public void Write(string message) - { - System.Console.Error.Write(message); - } + private readonly ConsoleLoggerOptions _consoleLoggerOptions; - public void WriteLine(string message) + public ConsoleOptionsMonitor() { - System.Console.Error.WriteLine(message); - } - } - - public class ConsoleErrorLoggerProvider : ILoggerProvider - { - private readonly Func _filter; - - private readonly ConcurrentDictionary _loggers = new ConcurrentDictionary(); - - private readonly ConsoleLoggerProcessor _messageQueue = new ConsoleLoggerProcessor(); - - private readonly static ConstructorInfo ConsoleLoggerConstructor = typeof(ConsoleLogger).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0]; - - public ConsoleErrorLoggerProvider(Func filter) - { - _filter = filter; - } - - public ILogger CreateLogger(string name) - { - return _loggers.GetOrAdd(name, CreateLoggerImplementation); + _consoleLoggerOptions = new ConsoleLoggerOptions() + { + LogToStandardErrorThreshold = LogLevel.Trace + }; } - private ConsoleLogger CreateLoggerImplementation(string name) - { - var consoleLogger = (ConsoleLogger)ConsoleLoggerConstructor.Invoke(new object[] { name, _filter, null, _messageQueue }); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - consoleLogger.Console = new WindowsLogErrorConsole(); - } - else - { - consoleLogger.Console = new AnsiLogConsole(new AnsiSystemErrorConsole()); - } + public ConsoleLoggerOptions CurrentValue => _consoleLoggerOptions; - return consoleLogger; - } + public ConsoleLoggerOptions Get(string name) => _consoleLoggerOptions; - public void Dispose() + public IDisposable OnChange(Action listener) { - _messageQueue.Dispose(); + return null; } - } + } } diff --git a/src/Dotnet.Script/Program.cs b/src/Dotnet.Script/Program.cs index 809fa63c..06edc471 100644 --- a/src/Dotnet.Script/Program.cs +++ b/src/Dotnet.Script/Program.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Runtime.Loader; using System.Threading.Tasks; namespace Dotnet.Script @@ -54,8 +55,9 @@ public static Func CreateLogFactory private static int Wain(string[] args) { - var app = new CommandLineApplication(throwOnUnexpectedArg: false) + var app = new CommandLineApplication() { + UnrecognizedArgumentHandling = UnrecognizedArgumentHandling.CollectAndContinue, ExtendedHelpText = "Starting without a path to a CSX file or a command, starts the REPL (interactive) mode." }; @@ -66,10 +68,11 @@ private static int Wain(string[] args) var debugMode = app.Option(DebugFlagShort + " | " + DebugFlagLong, "Enables debug output.", CommandOptionType.NoValue); var verbosity = app.Option("--verbosity", " Set the verbosity level of the command. Allowed values are t[trace], d[ebug], i[nfo], w[arning], e[rror], and c[ritical].", CommandOptionType.SingleValue); var nocache = app.Option("--no-cache", "Disable caching (Restore and Dll cache)", CommandOptionType.NoValue); + var isolatedLoadContext = app.Option("--isolated-load-context", "Use isolated assembly load context", CommandOptionType.NoValue); var infoOption = app.Option("--info", "Displays environmental information", CommandOptionType.NoValue); var argsBeforeDoubleHyphen = args.TakeWhile(a => a != "--").ToArray(); - var argsAfterDoubleHyphen = args.SkipWhile(a => a != "--").Skip(1).ToArray(); + var argsAfterDoubleHyphen = args.SkipWhile(a => a != "--").Skip(1).ToArray(); const string helpOptionTemplate = "-? | -h | --help"; app.HelpOption(helpOptionTemplate); @@ -81,15 +84,24 @@ private static int Wain(string[] args) var code = c.Argument("code", "Code to execute."); var cwd = c.Option("-cwd |--workingdirectory ", "Working directory for the code compiler. Defaults to current directory.", CommandOptionType.SingleValue); c.HelpOption(helpOptionTemplate); - c.OnExecute(async () => + c.OnExecuteAsync(async (cancellationToken) => { - if (string.IsNullOrWhiteSpace(code.Value)) + var source = code.Value; + if (string.IsNullOrWhiteSpace(source)) { - c.ShowHelp(); - return 0; + if (Console.IsInputRedirected) + { + source = await Console.In.ReadToEndAsync(); + } + else + { + c.ShowHelp(); + return 0; + } } + var logFactory = CreateLogFactory(verbosity.Value(), debugMode.HasValue()); - var options = new ExecuteCodeCommandOptions(code.Value,cwd.Value(), app.RemainingArguments.Concat(argsAfterDoubleHyphen).ToArray(),configuration.ValueEquals("release", StringComparison.OrdinalIgnoreCase) ? OptimizationLevel.Release : OptimizationLevel.Debug, nocache.HasValue(),packageSources.Values?.ToArray()); + var options = new ExecuteCodeCommandOptions(source, cwd.Value(), app.RemainingArguments.Concat(argsAfterDoubleHyphen).ToArray(), configuration.ValueEquals("release", StringComparison.OrdinalIgnoreCase) ? OptimizationLevel.Release : OptimizationLevel.Debug, nocache.HasValue(), packageSources.Values?.ToArray()); return await new ExecuteCodeCommand(ScriptConsole.Default, logFactory).Execute(options); }); }); @@ -176,7 +188,7 @@ private static int Wain(string[] args) ); var logFactory = CreateLogFactory(verbosity.Value(), debugMode.HasValue()); - new PublishCommand(ScriptConsole.Default, logFactory).Execute(options); + new PublishCommand(ScriptConsole.Default, logFactory).Execute(options); return 0; }); }); @@ -187,7 +199,7 @@ private static int Wain(string[] args) var dllPath = c.Argument("dll", "Path to DLL based script"); var commandDebugMode = c.Option(DebugFlagShort + " | " + DebugFlagLong, "Enables debug output.", CommandOptionType.NoValue); c.HelpOption(helpOptionTemplate); - c.OnExecute(async () => + c.OnExecuteAsync(async (cancellationToken) => { if (string.IsNullOrWhiteSpace(dllPath.Value)) { @@ -206,7 +218,7 @@ private static int Wain(string[] args) }); }); - app.OnExecute(async () => + app.OnExecuteAsync(async (cancellationToken) => { int exitCode = 0; @@ -221,11 +233,15 @@ private static int Wain(string[] args) return 0; } + AssemblyLoadContext assemblyLoadContext = null; + if (isolatedLoadContext.HasValue()) + assemblyLoadContext = new ScriptAssemblyLoadContext(); + if (scriptFile.HasValue) { if (interactive.HasValue()) { - return await RunInteractiveWithSeed(file.Value, logFactory, scriptArguments, packageSources.Values?.ToArray()); + return await RunInteractiveWithSeed(file.Value, logFactory, scriptArguments, packageSources.Values?.ToArray(), assemblyLoadContext); } var fileCommandOptions = new ExecuteScriptCommandOptions @@ -236,14 +252,21 @@ private static int Wain(string[] args) packageSources.Values?.ToArray(), interactive.HasValue(), nocache.HasValue() - ); + ) + { + AssemblyLoadContext = assemblyLoadContext + }; var fileCommand = new ExecuteScriptCommand(ScriptConsole.Default, logFactory); - return await fileCommand.Run(fileCommandOptions); - } + var result = await fileCommand.Run(fileCommandOptions); + if (Environment.ExitCode != 0) return Environment.ExitCode; + + return result; + + } else { - await RunInteractive(!nocache.HasValue(), logFactory, packageSources.Values?.ToArray()); + await RunInteractive(!nocache.HasValue(), logFactory, packageSources.Values?.ToArray(), assemblyLoadContext); } return exitCode; }); @@ -251,16 +274,22 @@ private static int Wain(string[] args) return app.Execute(argsBeforeDoubleHyphen); } - private static async Task RunInteractive(bool useRestoreCache, LogFactory logFactory, string[] packageSources) + private static async Task RunInteractive(bool useRestoreCache, LogFactory logFactory, string[] packageSources, AssemblyLoadContext assemblyLoadContext) { - var options = new ExecuteInteractiveCommandOptions(null, Array.Empty(), packageSources); + var options = new ExecuteInteractiveCommandOptions(null, Array.Empty(), packageSources) + { + AssemblyLoadContext = assemblyLoadContext + }; await new ExecuteInteractiveCommand(ScriptConsole.Default, logFactory).Execute(options); return 0; } - private async static Task RunInteractiveWithSeed(string file, LogFactory logFactory, string[] arguments, string[] packageSources) + private async static Task RunInteractiveWithSeed(string file, LogFactory logFactory, string[] arguments, string[] packageSources, AssemblyLoadContext assemblyLoadContext) { - var options = new ExecuteInteractiveCommandOptions(new ScriptFile(file), arguments, packageSources); + var options = new ExecuteInteractiveCommandOptions(new ScriptFile(file), arguments, packageSources) + { + AssemblyLoadContext = assemblyLoadContext + }; await new ExecuteInteractiveCommand(ScriptConsole.Default, logFactory).Execute(options); return 0; } diff --git a/src/dotnet-script.snk b/src/dotnet-script.snk new file mode 100644 index 00000000..23f209ff Binary files /dev/null and b/src/dotnet-script.snk differ diff --git a/src/omnisharp.json b/src/omnisharp.json new file mode 100644 index 00000000..8d9f5671 --- /dev/null +++ b/src/omnisharp.json @@ -0,0 +1,9 @@ +{ + "fileOptions": { + "systemExcludeSearchPatterns": [ + "**/TestFixtures/**/*", + "**/ScriptPackages/**/*" + ], + "excludeSearchPatterns": [] + } +} \ No newline at end of file