Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/QsCompiler/CommandLineTool/Commands/Build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public static IEnumerable<Example> UsageExamples
}
}

[Option("response-files", Required = true, SetName = RESPONSE_FILES,
HelpText = "Response file(s) providing the command arguments. Required only if no other arguments are specified. This option replaces all other arguments.")]
public IEnumerable<string> ResponseFiles { get; set; }

[Option('o', "output", Required = false, SetName = CODE_MODE,
HelpText = "Destination folder where the output of the compilation will be generated.")]
public string OutputFolder { get; set; }
Expand All @@ -59,6 +63,44 @@ public static IEnumerable<Example> UsageExamples
public bool EmitDll { get; set; }
}

/// <summary>
/// Given a string representing the command line arguments, splits them into a suitable string array.
/// </summary>
private static string[] SplitCommandLineArguments(string commandLine)
{
var parmChars = commandLine?.ToCharArray() ?? new char[0];
var inQuote = false;
for (int index = 0; index < parmChars.Length; index++)
{
var ignoreIfQuote = inQuote && parmChars[index - 1] == '\\';
if (parmChars[index] == '"' && !ignoreIfQuote) inQuote = !inQuote;
if (inQuote && parmChars[index] == '\n') parmChars[index] = ' ';
if (!inQuote && Char.IsWhiteSpace(parmChars[index])) parmChars[index] = '\n';
}
return (new string(parmChars)).Split('\n', StringSplitOptions.RemoveEmptyEntries);
}

/// <summary>
/// Reads the content off all given response files and tries to parse their concatenated content as command line arguments.
/// Logs a suitable exceptions and returns null if the parsing fails.
/// Throws an ArgumentNullException if the given sequence of responseFiles is null.
/// </summary>
private static BuildOptions FromResponseFiles(IEnumerable<string> responseFiles)
{
if (responseFiles == null) throw new ArgumentNullException(nameof(responseFiles));
var commandLine = String.Join(" ", responseFiles.Select(File.ReadAllText));
var args = SplitCommandLineArguments(commandLine);
var parsed = Parser.Default.ParseArguments<BuildOptions>(args);
return parsed.MapResult(
(BuildOptions opts) => opts,
(errs =>
{
HelpText.AutoBuild(parsed);
return null;
})
);
}


// publicly accessible routines

Expand All @@ -72,6 +114,10 @@ public static int Run(BuildOptions options, ConsoleLogger logger)
if (options == null) throw new ArgumentNullException(nameof(options));
if (logger == null) throw new ArgumentNullException(nameof(logger));

if (options?.ResponseFiles != null && options.ResponseFiles.Any())
{ options = FromResponseFiles(options.ResponseFiles); }
if (options == null) return ReturnCode.INVALID_ARGUMENTS;

var usesPlugins = options.Plugins != null && options.Plugins.Any();
var loadOptions = new CompilationLoader.Configuration
{
Expand Down
25 changes: 18 additions & 7 deletions src/QsCompiler/CommandLineTool/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ public enum LogFormat
// Note: items in one set are mutually exclusive with items from other sets
protected const string CODE_MODE = "codeMode";
protected const string SNIPPET_MODE = "snippetMode";
protected const string RESPONSE_FILES = "responseFiles";

[Option('v', "verbose", Required = false, Default = false,
HelpText = "Specifies whether to compile in verbose mode.")]
public bool Verbose { get; set; }
[Option('v', "verbosity", Required = false, Default = "normal",
HelpText = "Specifies the verbosity of the logged output. Valid values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].")]
public string Verbosity { get; set; }

[Option('i', "input", Required = true, SetName = CODE_MODE,
HelpText = "Q# code or name of the Q# file to compile.")]
Expand All @@ -41,15 +42,15 @@ public enum LogFormat
HelpText = "Q# snippet to compile - i.e. Q# code occuring within an operation or function declaration.")]
public string CodeSnippet { get; set; }

[Option('f', "withinFunction", Required = false, Default = false, SetName = SNIPPET_MODE,
[Option('f', "within-function", Required = false, Default = false, SetName = SNIPPET_MODE,
HelpText = "Specifies whether a given Q# snipped occurs within a function")]
public bool WithinFunction { get; set; }

[Option('r', "references", Required = false, Default = new string[0],
HelpText = "Referenced binaries to include in the compilation.")]
public IEnumerable<string> References { get; set; }

[Option('n', "noWarn", Required = false, Default = new int[0],
[Option('n', "no-warn", Required = false, Default = new int[0],
HelpText = "Warnings with the given code(s) will be ignored.")]
public IEnumerable<int> NoWarn { get; set; }

Expand Down Expand Up @@ -100,11 +101,21 @@ internal static Func<Diagnostic, string> LoggingFormat(LogFormat format)
/// Creates a suitable logger for the given command line options,
/// logging the given arguments if the verbosity is high enough.
/// </summary>
public ConsoleLogger GetLogger(DiagnosticSeverity minimumVerbosity = DiagnosticSeverity.Warning)
public ConsoleLogger GetLogger(DiagnosticSeverity defaultVerbosity = DiagnosticSeverity.Warning)
{
var verbosity =
"detailed".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"d".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"diagnostic".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"diag".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase)
? DiagnosticSeverity.Hint :
"quiet".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"q".Equals(this.Verbosity, StringComparison.InvariantCultureIgnoreCase)
? DiagnosticSeverity.Error :
defaultVerbosity;
var logger = new ConsoleLogger(
LoggingFormat(this.OutputFormat),
this.Verbose ? DiagnosticSeverity.Hint : minimumVerbosity,
verbosity,
this.NoWarn,
this.CodeSnippet != null ? -2 : 0);
this.Print(logger);
Expand Down
13 changes: 0 additions & 13 deletions src/QsCompiler/Compiler/CompilationLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -332,19 +332,6 @@ public CompilationLoader(SourceLoader loadSources, ReferenceLoader loadReference
this.LoadDiagnostics = ImmutableArray<Diagnostic>.Empty;
this.Config = options ?? new Configuration();

// We load all referenced .NET assemblies into the current context to
// for the sake of having a more resilient setup for loading rewrite steps.
loadReferences?.Invoke(refs =>
{
// no need to generate errors - this step is just a precaution
foreach (var dllPath in refs)
{
try { Assembly.LoadFrom(dllPath); }
catch { continue; }
}
return References.Empty;
});

Status rewriteStepLoading = Status.Succeeded;
this.ExternalRewriteSteps = RewriteSteps.Load(this.Config,
d => this.LogAndUpdateLoadDiagnostics(ref rewriteStepLoading, d),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<QsSourceFiles Include="**\*.qs" Exclude="@(QsSourceFiles)" />
</ItemGroup>
<PropertyGroup>
<QscCommand>$(QscExe) build -v --emit-dll --format MsBuild --proj "$(MSBuildProjectName)" -i "@(QsSourceFiles,'" "')" -r "@(QsReferences,'" "')" -o $(OutputPath)</QscCommand>
<QscCommand>$(QscExe) build --verbosity "Detailed" --emit-dll --format MsBuild --proj "$(MSBuildProjectName)" -i "@(QsSourceFiles,'" "')" -r "@(QsReferences,'" "')" -o $(OutputPath)</QscCommand>
</PropertyGroup>
<Exec Command="$(QscCommand)" IgnoreExitCode="false" />
</Target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<QsSourceFiles Include="**\*.qs" Exclude="@(QsSourceFiles)" />
</ItemGroup>
<PropertyGroup>
<QscCommand>$(QscExe) build -v --format MsBuild --proj "$(MSBuildProjectName)" -i "@(QsSourceFiles,'" "')" -r "@(QsReferences,'" "')" -o $(GenFilesDir) --load "$(SimulationTarget)"</QscCommand>
<QscCommand>$(QscExe) build -v diag --format MsBuild --proj "$(MSBuildProjectName)" -i "@(QsSourceFiles,'" "')" -r "@(QsReferences,'" "')" -o $(GenFilesDir) --load "$(SimulationTarget)"</QscCommand>
</PropertyGroup>
<Message Importance="low" Text="executing command '$(QscCommand)'" />
<Exec Command="$(QscCommand)" IgnoreExitCode="false" />
Expand Down
5 changes: 3 additions & 2 deletions src/QsCompiler/Tests.Compiler/CommandLineTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ let ``valid snippet`` () =
[|
"-s"
"let a = 0;"
"-v"
"-v n"
|]
|> testSnippet ReturnCode.SUCCESS

Expand All @@ -57,7 +57,8 @@ let ``one valid file`` () =
[|
"-i"
("TestCases","General.qs") |> Path.Combine
"-v"
"--verbosity"
"Diagnostic"
|]
|> testInput ReturnCode.SUCCESS

Expand Down
2 changes: 2 additions & 0 deletions src/QuantumSdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Steps defined within packages or projects with higher priority are executed firs

[comment]: # (TODO: describe how to limit included rewrite steps to a particular execution target)

If you develop a NuGet package to extend the Q# compilation process, we recommend to distribute it as a self-contained package to avoid issues due to references that could not be resolved. Each qsc reference is loaded into its own context to avoid issues when several references depend on different versions of the same package.

### Injected C# code ###

It is possible to inject C# code into Q# packages e.g. for integration purposes. By default the Sdk is configured to do just that, see also the section on [defined properties](#defined-project-properties). That code may be generated as part of a custom rewrite step, e.g. if the code generation requires information about the Q# compilation.
Expand Down
5 changes: 5 additions & 0 deletions src/QuantumSdk/Sdk/Sdk.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk"/>
<Import Project="$(MSBuildThisFileDirectory)../ProjectSystem/ProjectSystem.props"/>

<!-- We want to keep this here as fallback to mitigate issues when loading qsc references. -->
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>


<!-- The following item groups indeed needs to live in this file to ensure
that they evaluated before the modifications in the project file are applied. -->
Expand Down
19 changes: 10 additions & 9 deletions src/QuantumSdk/Sdk/Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
</MSBuildAllProjects>
</PropertyGroup>

<!-- We want to keep this here as fallback to mitigate issues when loading qsc references. -->
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

<Import Project="$(MSBuildThisFileDirectory)../DefaultItems/DefaultItems.targets"/>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk"/>
<Import Project="$(MSBuildThisFileDirectory)../ProjectSystem/ProjectSystem.targets"/>
Expand Down Expand Up @@ -72,7 +67,7 @@
<MakeDir Condition="$(QsharpDocsGeneration)" Directories="$(QsharpDocsOutputPath)" />
<!-- generate a suitable build configuration for the Q# command line compiler (qsc) -->
<PropertyGroup>
<_VerbosityFlag Condition="'$(QscVerbosity)' == 'Detailed' Or '$(QscVerbosity)' == 'Diagnostic'">-v</_VerbosityFlag>
<_VerbosityFlag Condition="'$(QscVerbosity)' != ''">-v $(QscVerbosity)</_VerbosityFlag>
<_BuildConfigFile>$(QscBuildConfigOutputPath)qsc.config</_BuildConfigFile>
<_BuildConfigGenerationOutputFlag>--output "$(_BuildConfigFile)"</_BuildConfigGenerationOutputFlag>
<_BuildConfigGenerationQscReferencesFlag Condition="@(ResolvedQscReferencesAndPriorities->Count()) &gt; 0">--QscReferences "@(ResolvedQscReferencesAndPriorities,'" "')"</_BuildConfigGenerationQscReferencesFlag>
Expand All @@ -84,7 +79,7 @@
<Output TaskParameter="Lines" ItemName="_PrioritizedResolvedQscReferences"/>
</ReadLinesFromFile>
<ItemGroup>
<ResolvedQsharpReferences Include="@(ReferencePath)" /> <!-- for the sake of a more robust loading of qsc references, we pass along all references -->
<ResolvedQsharpReferences Include="@(ReferencePath)" Condition="$([System.Text.RegularExpressions.Regex]::IsMatch(%(FullPath), '(?i)system.|mscorlib|netstandard.library|microsoft.netcore.app|csharp|fsharp|microsoft.visualstudio|microsoft.testplatform|microsoft.codeanalysis|fparsec|newtonsoft|roslynwrapper|yamldotnet')) == false" />
</ItemGroup>
<!-- invoke the Q# command line compiler -->
<PropertyGroup>
Expand All @@ -94,15 +89,21 @@
<_QscCommandReferencesFlag Condition="@(ResolvedQsharpReferences->Count()) &gt; 0">--references "@(ResolvedQsharpReferences,'" "')"</_QscCommandReferencesFlag>
<_QscCommandLoadFlag Condition="@(_PrioritizedResolvedQscReferences->Count()) &gt; 0">--load "@(_PrioritizedResolvedQscReferences,'" "')"</_QscCommandLoadFlag>
<_QscPackageLoadFallbackFoldersFlag Condition="'$(OutputPath)' != '' And '$(MSBuildProjectDirectory)' != ''">--package-load-fallback-folders $(MSBuildProjectDirectory)/$(OutputPath)</_QscPackageLoadFallbackFoldersFlag>
<QscCommand>$(QscExe) build --proj "$(PathCompatibleAssemblyName)" --format MsBuild $(_VerbosityFlag) $(_QscCommandDocsFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscPackageLoadFallbackFoldersFlag)</QscCommand>
<_QscCommandArgs>--proj "$(PathCompatibleAssemblyName)" --format MsBuild $(_VerbosityFlag) $(_QscCommandDocsFlag) $(_QscCommandInputFlag) $(_QscCommandOutputFlag) $(_QscCommandReferencesFlag) $(_QscCommandLoadFlag) $(_QscPackageLoadFallbackFoldersFlag)</_QscCommandArgs>
<_QscCommandArgsFile>$(QscBuildConfigOutputPath)qsc.rsp</_QscCommandArgsFile>
<QscCommand>$(QscExe) build $(_QscCommandArgs)</QscCommand>
</PropertyGroup>
<WriteLinesToFile File="$(GeneratedFilesOutputPath)qsc-command.txt" Lines=":: files ::;@(QsharpCompile);:: references ::;@(ResolvedQsharpReferences);:: loaded ::;@(_PrioritizedResolvedQscReferences);:: command ::;$(QscCommand)" Overwrite="true"/>
<WriteLinesToFile File="$(_QscCommandArgsFile)" Lines="$(_QscCommandArgs)" Overwrite="true"/> <!-- we use a response file to avoid issues when the command gets long -->
<Exec Command="$(QscCommand)" IgnoreExitCode="false" />
<!-- configure the dll built by the C# compiler -->
<ItemGroup>
<EmbeddedResource Include="$(GeneratedFilesOutputPath)$(PathCompatibleAssemblyName).bson" LogicalName="__qsharp_data__.bson" Visible="false" />
<Compile Condition="$(CsharpGeneration)" Include="$(GeneratedFilesOutputPath)**/*.g.cs" Exclude="@(Compile)" AutoGen="true" />
</ItemGroup>
<Message
Condition="$(CsharpGeneration) And ('$(QscVerbosity)' == 'Detailed' Or '$(QscVerbosity)' == 'Diagnostic')"
Text="C# files to compile: @(Compile)"
Importance="High" />
</Target>


Expand Down
13 changes: 10 additions & 3 deletions src/QuantumSdk/Tools/BuildConfiguration/Generate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.IO;
using System.Linq;
using System.Text;


namespace Microsoft.Quantum.Sdk.Tools
Expand All @@ -17,7 +18,13 @@ public static partial class BuildConfiguration
public static ReturnCode Generate(Options options)
{
if (options == null) return ReturnCode.MISSING_ARGUMENTS;
(string, int) ParseQscReference(string qscRef)
var verbose =
"detailed".Equals(options.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"d".Equals(options.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"diagnostic".Equals(options.Verbosity, StringComparison.InvariantCultureIgnoreCase) ||
"diag".Equals(options.Verbosity, StringComparison.InvariantCultureIgnoreCase);

static (string, int) ParseQscReference(string qscRef)
{
var pieces = qscRef.Trim().TrimStart('(').TrimEnd(')').Split(',');
var path = pieces.First().Trim();
Expand All @@ -38,11 +45,11 @@ public static ReturnCode Generate(Options options)
var errMsg = $"Could not parse the given Qsc references. " +
$"Expecting a string of the form \"(pathToDll, priority)\" for each qsc reference.";
Console.WriteLine(errMsg);
if (options.Verbose) Console.WriteLine(ex);
if (verbose) Console.WriteLine(ex);
return ReturnCode.INVALID_ARGUMENTS;
}

return BuildConfiguration.WriteConfigFile(options.OutputFile, orderedQscReferences, options.Verbose)
return BuildConfiguration.WriteConfigFile(options.OutputFile, orderedQscReferences, verbose)
? ReturnCode.SUCCESS
: ReturnCode.IO_EXCEPTION;
}
Expand Down
6 changes: 3 additions & 3 deletions src/QuantumSdk/Tools/BuildConfiguration/Options.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ namespace Microsoft.Quantum.Sdk.Tools
{
public class Options
{
[Option('v', "verbose", Required = false, Default = false,
HelpText = "Specifies whether to turn on verbose mode.")]
public bool Verbose { get; set; }
[Option('v', "verbosity", Required = false, Default = "Normal",
HelpText = "Specifies the verbosity of the logged output. Valid values are q[uiet], m[inimal], n[ormal], d[etailed], and diag[nostic].")]
public string Verbosity { get; set; }

[Option("QscReferences", Required = false,
HelpText = ".NET Core assemblies containing rewrite steps to be passed to the Q# compiler. " +
Expand Down