From 44fd8bc2de3274704e16154a91e8e0222f89b435 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Mon, 11 Apr 2016 19:25:28 -0700 Subject: [PATCH] Updated ProjectModel - Added PackOptions, RuntimeOptions, PublishOptions and updated CompilationOptions - Added IncludeFilesResolver to parse include, exclude patterns - Added compile, embed and copyToOutput to compilationOptions - Renamed compilationOptions to buildOptions - Moved compilerName into buildOptions - This change is backwards compatible - Added warnings to be shown when the old schema is used - Handled diagnostic messages in ProjectReader - Added unit and end to end tests --- .../TestProjects/EndToEndTestApp/Program.cs | 13 ++ .../EndToEndTestApp/copy/file.txt | 0 .../EndToEndTestApp/copy/fileex.txt | 0 .../EndToEndTestApp/packfiles/pack1.txt | 0 .../EndToEndTestApp/packfiles/pack2.txt | 0 .../TestProjects/EndToEndTestApp/project.json | 31 +++ .../EndToEndTestApp/resource1.resx | 4 + .../EndToEndTestApp/resource2.resx | 4 + .../EndToEndTestApp/testpublishfile.txt | 0 .../Executable.cs | 21 +- .../ProjectContextExtensions.cs | 14 +- src/Microsoft.DotNet.Files/ContentFiles.cs | 24 ++ .../ProjectJsonWorkspace.cs | 18 +- .../CommonCompilerOptions.cs | 49 +++- .../Compilation/LibraryExporter.cs | 17 +- .../CompilationOutputFiles.cs | 37 ++- .../ErrorCodes.DotNet.cs | 6 + .../Files/IncludeContext.cs | 215 +++++++++++++++++ .../Files/IncludeEntry.cs | 45 ++++ .../Files/IncludeFilesResolver.cs | 182 +++++++++++++++ .../Files/PackIncludeEntry.cs | 4 +- .../Files/PatternsCollectionHelper.cs | 66 +----- .../Files/ProjectFilesCollection.cs | 2 - .../PackOptions.cs | 32 +++ src/Microsoft.DotNet.ProjectModel/Project.cs | 31 +-- .../ProjectContextBuilder.cs | 8 +- .../ProjectExtensions.cs | 2 +- .../ProjectReader.cs | 212 ++++++++++++++--- .../RuntimeOptions.cs | 12 + .../RuntimeOutputFiles.cs | 4 +- .../WorkspaceContext.cs | 3 +- .../dotnet-build/CompilerIOManager.cs | 45 +++- .../IncrementalPreconditionManager.cs | 3 +- .../commands/dotnet-build/ProjectBuilder.cs | 17 +- .../commands/dotnet-compile/Compiler.cs | 30 ++- .../commands/dotnet-compile/CompilerUtil.cs | 70 +++++- .../dotnet-compile/ManagedCompiler.cs | 8 +- .../dotnet-pack/BuildProjectCommand.cs | 24 +- .../commands/dotnet-pack/PackageGenerator.cs | 68 ++++-- .../dotnet-pack/SymbolPackageGenerator.cs | 42 +++- .../InternalModels/ProjectContextSnapshot.cs | 19 +- .../commands/dotnet-publish/PublishCommand.cs | 61 ++--- ...hatIWantToCreateFileCollectionsFromJson.cs | 1 - ...ThatIWantToCreateIncludeEntriesFromJson.cs | 216 ++++++++++++++++++ .../GivenThatIWantToLoadAProjectJsonFile.cs | 137 +++++++++-- .../LibraryExporterPackageTests.cs | 5 +- test/dotnet-compile.Tests/CompilerTests.cs | 42 ++++ test/dotnet-pack.Tests/PackTests.cs | 18 ++ test/dotnet-publish.Tests/PublishTests.cs | 15 ++ 49 files changed, 1600 insertions(+), 277 deletions(-) create mode 100644 TestAssets/TestProjects/EndToEndTestApp/Program.cs create mode 100644 TestAssets/TestProjects/EndToEndTestApp/copy/file.txt create mode 100644 TestAssets/TestProjects/EndToEndTestApp/copy/fileex.txt create mode 100644 TestAssets/TestProjects/EndToEndTestApp/packfiles/pack1.txt create mode 100644 TestAssets/TestProjects/EndToEndTestApp/packfiles/pack2.txt create mode 100644 TestAssets/TestProjects/EndToEndTestApp/project.json create mode 100644 TestAssets/TestProjects/EndToEndTestApp/resource1.resx create mode 100644 TestAssets/TestProjects/EndToEndTestApp/resource2.resx create mode 100644 TestAssets/TestProjects/EndToEndTestApp/testpublishfile.txt create mode 100644 src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs create mode 100644 src/Microsoft.DotNet.ProjectModel/Files/IncludeEntry.cs create mode 100644 src/Microsoft.DotNet.ProjectModel/Files/IncludeFilesResolver.cs create mode 100644 src/Microsoft.DotNet.ProjectModel/PackOptions.cs create mode 100644 src/Microsoft.DotNet.ProjectModel/RuntimeOptions.cs create mode 100644 test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateIncludeEntriesFromJson.cs diff --git a/TestAssets/TestProjects/EndToEndTestApp/Program.cs b/TestAssets/TestProjects/EndToEndTestApp/Program.cs new file mode 100644 index 000000000..28e184cb5 --- /dev/null +++ b/TestAssets/TestProjects/EndToEndTestApp/Program.cs @@ -0,0 +1,13 @@ +using System; + +namespace ConsoleApplication +{ + public class Program + { + public static int Main(string[] args) + { + Console.WriteLine("Hello World!"); + return 100; + } + } +} diff --git a/TestAssets/TestProjects/EndToEndTestApp/copy/file.txt b/TestAssets/TestProjects/EndToEndTestApp/copy/file.txt new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/EndToEndTestApp/copy/fileex.txt b/TestAssets/TestProjects/EndToEndTestApp/copy/fileex.txt new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/EndToEndTestApp/packfiles/pack1.txt b/TestAssets/TestProjects/EndToEndTestApp/packfiles/pack1.txt new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/EndToEndTestApp/packfiles/pack2.txt b/TestAssets/TestProjects/EndToEndTestApp/packfiles/pack2.txt new file mode 100644 index 000000000..e69de29bb diff --git a/TestAssets/TestProjects/EndToEndTestApp/project.json b/TestAssets/TestProjects/EndToEndTestApp/project.json new file mode 100644 index 000000000..2b2a97584 --- /dev/null +++ b/TestAssets/TestProjects/EndToEndTestApp/project.json @@ -0,0 +1,31 @@ +{ + "version": "1.0.0-*", + "buildOptions": { + "emitEntryPoint": true, + "embed": { + "include": "*.resx", + "mappings": { + "myresource.resx": "resource2.resx" + } + }, + "copyToOutput": { + "include": "copy/*.txt", + "excludeFiles": "copy/fileex.txt" + } + }, + "dependencies": { + "Microsoft.NETCore.App": "1.0.0-rc2-*" + }, + "packOptions": { + "files": { + "includeFiles": "packfiles/pack1.txt", + "mappings": { + "newpath/": "packfiles/pack2.txt" + } + } + }, + "publishOptions": "testpublishfile.txt", + "frameworks": { + "netcoreapp1.0": { } + } +} diff --git a/TestAssets/TestProjects/EndToEndTestApp/resource1.resx b/TestAssets/TestProjects/EndToEndTestApp/resource1.resx new file mode 100644 index 000000000..8db59de8e --- /dev/null +++ b/TestAssets/TestProjects/EndToEndTestApp/resource1.resx @@ -0,0 +1,4 @@ + + +Some content + \ No newline at end of file diff --git a/TestAssets/TestProjects/EndToEndTestApp/resource2.resx b/TestAssets/TestProjects/EndToEndTestApp/resource2.resx new file mode 100644 index 000000000..8db59de8e --- /dev/null +++ b/TestAssets/TestProjects/EndToEndTestApp/resource2.resx @@ -0,0 +1,4 @@ + + +Some content + \ No newline at end of file diff --git a/TestAssets/TestProjects/EndToEndTestApp/testpublishfile.txt b/TestAssets/TestProjects/EndToEndTestApp/testpublishfile.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/Microsoft.DotNet.Compiler.Common/Executable.cs b/src/Microsoft.DotNet.Compiler.Common/Executable.cs index 8b1cb396b..563264973 100644 --- a/src/Microsoft.DotNet.Compiler.Common/Executable.cs +++ b/src/Microsoft.DotNet.Compiler.Common/Executable.cs @@ -6,17 +6,17 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Xml.Linq; -using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Files; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.DotNet.Tools.Common; using Microsoft.Extensions.DependencyModel; using NuGet.Frameworks; -using Newtonsoft.Json.Linq; using Newtonsoft.Json; -using System.Reflection.PortableExecutable; +using Newtonsoft.Json.Linq; namespace Microsoft.DotNet.Cli.Compiler.Common { @@ -88,7 +88,20 @@ namespace Microsoft.DotNet.Cli.Compiler.Common private void CopyContentFiles() { var contentFiles = new ContentFiles(_context); - contentFiles.StructuredCopyTo(_runtimeOutputPath); + + if (_compilerOptions.CopyToOutputInclude != null) + { + var includeEntries = IncludeFilesResolver.GetIncludeFiles( + _compilerOptions.CopyToOutputInclude, + PathUtility.EnsureTrailingSlash(_runtimeOutputPath), + diagnostics: null); + + contentFiles.StructuredCopyTo(_runtimeOutputPath, includeEntries); + } + else + { + contentFiles.StructuredCopyTo(_runtimeOutputPath); + } } private void CopyAssemblies(IEnumerable libraryExports) diff --git a/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs b/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs index d0cc3bb1e..d51ec02e9 100644 --- a/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs +++ b/src/Microsoft.DotNet.Compiler.Common/ProjectContextExtensions.cs @@ -22,7 +22,7 @@ namespace Microsoft.DotNet.Cli.Compiler.Common var baseOption = context.ProjectFile.GetCompilerOptions(framework, configurationName); IReadOnlyList defaultSuppresses; - var compilerName = context.ProjectFile.CompilerName ?? "csc"; + var compilerName = baseOption.CompilerName ?? "csc"; if (DefaultCompilerWarningSuppresses.Suppresses.TryGetValue(compilerName, out defaultSuppresses)) { baseOption.SuppressWarnings = (baseOption.SuppressWarnings ?? Enumerable.Empty()).Concat(defaultSuppresses).Distinct(); @@ -46,22 +46,22 @@ namespace Microsoft.DotNet.Cli.Compiler.Common // used in incremental compilation for the key file public static CommonCompilerOptions ResolveCompilationOptions(this ProjectContext context, string configuration) { - var compilationOptions = context.GetLanguageSpecificCompilerOptions(context.TargetFramework, configuration); + var compilerOptions = context.GetLanguageSpecificCompilerOptions(context.TargetFramework, configuration); // Path to strong naming key in environment variable overrides path in project.json var environmentKeyFile = Environment.GetEnvironmentVariable(EnvironmentNames.StrongNameKeyFile); if (!string.IsNullOrWhiteSpace(environmentKeyFile)) { - compilationOptions.KeyFile = environmentKeyFile; + compilerOptions.KeyFile = environmentKeyFile; } - else if (!string.IsNullOrWhiteSpace(compilationOptions.KeyFile)) + else if (!string.IsNullOrWhiteSpace(compilerOptions.KeyFile)) { // Resolve full path to key file - compilationOptions.KeyFile = - Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilationOptions.KeyFile)); + compilerOptions.KeyFile = + Path.GetFullPath(Path.Combine(context.ProjectFile.ProjectDirectory, compilerOptions.KeyFile)); } - return compilationOptions; + return compilerOptions; } } } diff --git a/src/Microsoft.DotNet.Files/ContentFiles.cs b/src/Microsoft.DotNet.Files/ContentFiles.cs index 47165f320..b6e5ada0a 100644 --- a/src/Microsoft.DotNet.Files/ContentFiles.cs +++ b/src/Microsoft.DotNet.Files/ContentFiles.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.Tools.Common; namespace Microsoft.DotNet.Files @@ -60,6 +61,29 @@ namespace Microsoft.DotNet.Files RemoveAttributeFromFiles(pathMap.Values, FileAttributes.ReadOnly); } + public void StructuredCopyTo(string targetDirectory, IEnumerable includeEntries) + { + if (includeEntries == null) + { + return; + } + + foreach (var targetDir in includeEntries + .Select(f => Path.GetDirectoryName(f.TargetPath)) + .Distinct() + .Where(t => !Directory.Exists(t))) + { + Directory.CreateDirectory(targetDir); + } + + foreach (var file in includeEntries) + { + File.Copy(file.SourcePath, file.TargetPath, overwrite: true); + } + + RemoveAttributeFromFiles(includeEntries.Select(f => f.TargetPath), FileAttributes.ReadOnly); + } + private static void RemoveAttributeFromFiles(IEnumerable files, FileAttributes attribute) { foreach (var file in files) diff --git a/src/Microsoft.DotNet.ProjectModel.Workspaces/ProjectJsonWorkspace.cs b/src/Microsoft.DotNet.ProjectModel.Workspaces/ProjectJsonWorkspace.cs index 34ba54784..d49dbf80c 100644 --- a/src/Microsoft.DotNet.ProjectModel.Workspaces/ProjectJsonWorkspace.cs +++ b/src/Microsoft.DotNet.ProjectModel.Workspaces/ProjectJsonWorkspace.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Text; using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.Extensions.PlatformAbstractions; using NuGet.Frameworks; @@ -69,15 +70,26 @@ namespace Microsoft.DotNet.ProjectModel.Workspaces // TODO: ctor argument? var configuration = "Debug"; - var compilationOptions = project.GetLanguageSpecificCompilerOptions(project.TargetFramework, configuration); + var compilerOptions = project.GetLanguageSpecificCompilerOptions(project.TargetFramework, configuration); - var compilationSettings = ToCompilationSettings(compilationOptions, project.TargetFramework, project.ProjectFile.ProjectDirectory); + var compilationSettings = ToCompilationSettings(compilerOptions, project.TargetFramework, project.ProjectFile.ProjectDirectory); OnParseOptionsChanged(projectInfo.Id, new CSharpParseOptions(compilationSettings.LanguageVersion, preprocessorSymbols: compilationSettings.Defines)); OnCompilationOptionsChanged(projectInfo.Id, compilationSettings.CompilationOptions); - foreach (var file in project.ProjectFile.Files.SourceFiles) + IEnumerable sourceFiles = null; + if (compilerOptions.CompileInclude == null) + { + sourceFiles = project.ProjectFile.Files.SourceFiles; + } + else + { + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null); + sourceFiles = includeFiles.Select(f => f.SourcePath); + } + + foreach (var file in sourceFiles) { using (var stream = File.OpenRead(file)) { diff --git a/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs b/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs index 1a1d3dc95..9411a2757 100644 --- a/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs +++ b/src/Microsoft.DotNet.ProjectModel/CommonCompilerOptions.cs @@ -1,9 +1,9 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Collections.Generic; using System.Linq; +using Microsoft.DotNet.ProjectModel.Files; namespace Microsoft.DotNet.ProjectModel { @@ -39,7 +39,15 @@ namespace Microsoft.DotNet.ProjectModel public IEnumerable AdditionalArguments { get; set; } - public string OutputName { get;set; } + public string OutputName { get; set; } + + public string CompilerName { get; set; } + + public IncludeContext CompileInclude { get; set; } + + public IncludeContext EmbedInclude { get; set; } + + public IncludeContext CopyToOutputInclude { get; set; } public override bool Equals(object obj) { @@ -60,7 +68,21 @@ namespace Microsoft.DotNet.ProjectModel EnumerableEquals(Defines, other.Defines) && EnumerableEquals(SuppressWarnings, other.SuppressWarnings) && EnumerableEquals(AdditionalArguments, other.AdditionalArguments) && - OutputName == other.OutputName; + OutputName == other.OutputName && + CompilerName == other.CompilerName && + IsEqual(CompileInclude, other.CompileInclude) && + IsEqual(EmbedInclude, other.EmbedInclude) && + IsEqual(CopyToOutputInclude, other.CopyToOutputInclude); + } + + private static bool IsEqual(IncludeContext first, IncludeContext second) + { + if (first == null || second == null) + { + return first == second; + } + + return first.Equals(second); } private static bool EnumerableEquals(IEnumerable left, IEnumerable right) @@ -161,6 +183,27 @@ namespace Microsoft.DotNet.ProjectModel { result.OutputName = option.OutputName; } + + if (option.CompileInclude != null) + { + result.CompileInclude = option.CompileInclude; + } + + if (option.EmbedInclude != null) + { + result.EmbedInclude = option.EmbedInclude; + } + + if (option.CopyToOutputInclude != null) + { + result.CopyToOutputInclude = option.CopyToOutputInclude; + } + + // compilerName set in the root cannot be overriden. + if (result.CompilerName == null) + { + result.CompilerName = option.CompilerName; + } } return result; diff --git a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs index 5966f46e8..ce20761b9 100644 --- a/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs +++ b/src/Microsoft.DotNet.ProjectModel/Compilation/LibraryExporter.cs @@ -279,6 +279,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation private LibraryExport ExportProject(ProjectDescription project) { var builder = LibraryExportBuilder.Create(project); + var compilerOptions = project.Project.GetCompilerOptions(project.TargetFrameworkInfo.FrameworkName, _configuration); if (!string.IsNullOrEmpty(project.TargetFrameworkInfo?.AssemblyPath)) { @@ -298,7 +299,7 @@ namespace Microsoft.DotNet.ProjectModel.Compilation builder.AddRuntimeAsset(new LibraryAsset(Path.GetFileName(pdbPath), Path.GetFileName(pdbPath), pdbPath)); } } - else if (project.Project.Files.SourceFiles.Any()) + else if (HasSourceFiles(project)) { var outputPaths = project.GetOutputPaths(_buildBasePath, _solutionRootPath, _configuration, _runtime); @@ -336,6 +337,20 @@ namespace Microsoft.DotNet.ProjectModel.Compilation return builder.Build(); } + private bool HasSourceFiles(ProjectDescription project) + { + var compilerOptions = project.TargetFrameworkInfo.CompilerOptions; + + if (compilerOptions.CompileInclude == null) + { + return project.Project.Files.SourceFiles.Any(); + } + + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null); + + return includeFiles.Any(); + } + private IEnumerable CollectAssets(CompilationOutputFiles files) { var assemblyPath = files.Assembly; diff --git a/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs b/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs index 90eeec5a7..463451d4f 100644 --- a/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs +++ b/src/Microsoft.DotNet.ProjectModel/CompilationOutputFiles.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.ProjectModel.Resources; using NuGet.Frameworks; @@ -27,8 +28,8 @@ namespace Microsoft.DotNet.ProjectModel Framework = framework; OutputExtension = FileNameSuffixes.DotNet.DynamicLib; - var compilationOptions = Project.GetCompilerOptions(framework, configuration); - if (framework.IsDesktop() && compilationOptions.EmitEntryPoint.GetValueOrDefault()) + var compilerOptions = Project.GetCompilerOptions(framework, configuration); + if (framework.IsDesktop() && compilerOptions.EmitEntryPoint.GetValueOrDefault()) { OutputExtension = FileNameSuffixes.DotNet.Exe; } @@ -40,9 +41,9 @@ namespace Microsoft.DotNet.ProjectModel { get { - var compilationOptions = Project.GetCompilerOptions(Framework, Configuration); + var compilerOptions = Project.GetCompilerOptions(Framework, Configuration); - return Path.Combine(BasePath, compilationOptions.OutputName + OutputExtension); + return Path.Combine(BasePath, compilerOptions.OutputName + OutputExtension); } } @@ -58,14 +59,17 @@ namespace Microsoft.DotNet.ProjectModel public virtual IEnumerable Resources() { - var resourceNames = Project.Files.ResourceFiles - .Select(f => ResourceUtility.GetResourceCultureName(f.Key)) + var resourceCultureNames = GetResourceFiles() + .Select(f => ResourceUtility.GetResourceCultureName(f)) .Where(f => !string.IsNullOrEmpty(f)) .Distinct(); - foreach (var resourceName in resourceNames) + foreach (var resourceCultureName in resourceCultureNames) { - yield return new ResourceFile(Path.Combine(BasePath, resourceName, Project.Name + ".resources" + FileNameSuffixes.DotNet.DynamicLib), resourceName); + yield return new ResourceFile( + Path.Combine( + BasePath, resourceCultureName, Project.Name + ".resources" + FileNameSuffixes.DotNet.DynamicLib), + resourceCultureName); } } @@ -73,8 +77,8 @@ namespace Microsoft.DotNet.ProjectModel { yield return Assembly; yield return PdbPath; - var compilationOptions = Project.GetCompilerOptions(Framework, Configuration); - if (compilationOptions.GenerateXmlDocumentation == true) + var compilerOptions = Project.GetCompilerOptions(Framework, Configuration); + if (compilerOptions.GenerateXmlDocumentation == true) { yield return Path.ChangeExtension(Assembly, "xml"); } @@ -83,5 +87,18 @@ namespace Microsoft.DotNet.ProjectModel yield return resource.Path; } } + + private IEnumerable GetResourceFiles() + { + var compilerOptions = Project.GetCompilerOptions(Framework, Configuration); + if (compilerOptions.EmbedInclude == null) + { + return Project.Files.ResourceFiles.Keys; + } + + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.EmbedInclude, "/", diagnostics: null); + + return includeFiles.Select(f => f.SourcePath); + } } } \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/ErrorCodes.DotNet.cs b/src/Microsoft.DotNet.ProjectModel/ErrorCodes.DotNet.cs index 83e229eef..df804cb1b 100644 --- a/src/Microsoft.DotNet.ProjectModel/ErrorCodes.DotNet.cs +++ b/src/Microsoft.DotNet.ProjectModel/ErrorCodes.DotNet.cs @@ -16,5 +16,11 @@ namespace Microsoft.DotNet.ProjectModel // Failed to read lock file public static readonly string DOTNET1014 = nameof(DOTNET1014); + + // The '{0}' option is deprecated. Use '{1}' instead. + public static readonly string DOTNET1015 = nameof(DOTNET1015); + + // The '{0}' option in the root is deprecated. Use it in '{1}' instead. + public static readonly string DOTNET1016 = nameof(DOTNET1016); } } diff --git a/src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs b/src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs new file mode 100644 index 000000000..c7b6150ec --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/Files/IncludeContext.cs @@ -0,0 +1,215 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.ProjectModel.Files +{ + public class IncludeContext + { + private static readonly char[] PatternSeparator = new[] { ';' }; + + public IncludeContext( + string sourceBasePath, + string option, + JObject rawObject, + string[] defaultBuiltInInclude, + string[] defaultBuiltInExclude) + { + if (sourceBasePath == null) + { + throw new ArgumentNullException(nameof(sourceBasePath)); + } + + if (option == null) + { + throw new ArgumentNullException(nameof(option)); + } + + if (rawObject == null) + { + throw new ArgumentNullException(nameof(rawObject)); + } + + SourceBasePath = sourceBasePath; + Option = option; + var token = rawObject.Value(option); + if (token.Type != JTokenType.Object) + { + IncludePatterns = new List(ExtractValues(token)); + } + else + { + IncludePatterns = CreateCollection( + sourceBasePath, "include", ExtractValues(token.Value("include")), literalPath: false); + + ExcludePatterns = CreateCollection( + sourceBasePath, "exclude", ExtractValues(token.Value("exclude")), literalPath: false); + + IncludeFiles = CreateCollection( + sourceBasePath, "includeFiles", ExtractValues(token.Value("includeFiles")), literalPath: true); + + ExcludeFiles = CreateCollection( + sourceBasePath, "excludeFiles", ExtractValues(token.Value("excludeFiles")), literalPath: true); + + var builtIns = token.Value("builtIns") as JObject; + if (builtIns != null) + { + BuiltInsInclude = CreateCollection( + sourceBasePath, "include", ExtractValues(builtIns.Value("include")), literalPath: false); + + if (defaultBuiltInInclude != null && !BuiltInsInclude.Any()) + { + BuiltInsInclude = defaultBuiltInInclude.ToList(); + } + + BuiltInsExclude = CreateCollection( + sourceBasePath, "exclude", ExtractValues(builtIns.Value("exclude")), literalPath: false); + + if (defaultBuiltInExclude != null && !BuiltInsExclude.Any()) + { + BuiltInsExclude = defaultBuiltInExclude.ToList(); + } + } + + var mappings = token.Value("mappings") as JObject; + if (mappings != null) + { + Mappings = new Dictionary(); + + foreach (var map in mappings) + { + Mappings.Add( + map.Key, + new IncludeContext( + sourceBasePath, + map.Key, + mappings, + defaultBuiltInInclude: null, + defaultBuiltInExclude: null)); + } + } + } + } + + public string SourceBasePath { get; } + + public string Option { get; } + + public List IncludePatterns { get; } + + public List ExcludePatterns { get; } + + public List IncludeFiles { get; } + + public List ExcludeFiles { get; } + + public List BuiltInsInclude { get; } + + public List BuiltInsExclude { get; } + + public IDictionary Mappings { get; } + + public override bool Equals(object obj) + { + var other = obj as IncludeContext; + return other != null && + SourceBasePath == other.SourceBasePath && + Option == other.Option && + EnumerableEquals(IncludePatterns, other.IncludePatterns) && + EnumerableEquals(ExcludePatterns, other.ExcludePatterns) && + EnumerableEquals(IncludeFiles, other.IncludeFiles) && + EnumerableEquals(ExcludeFiles, other.ExcludeFiles) && + EnumerableEquals(BuiltInsInclude, other.BuiltInsInclude) && + EnumerableEquals(BuiltInsExclude, other.BuiltInsExclude) && + EnumerableEquals(Mappings, other.Mappings); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + private static bool EnumerableEquals(IEnumerable left, IEnumerable right) + => Enumerable.SequenceEqual(left ?? EmptyArray.Value, right ?? EmptyArray.Value); + + private static string[] ExtractValues(JToken token) + { + if (token != null) + { + if (token.Type == JTokenType.String) + { + return new string[] { token.Value() }; + } + else if (token.Type == JTokenType.Array) + { + return token.Values().ToArray(); + } + } + + return new string[0]; + } + + internal static List CreateCollection( + string projectDirectory, + string propertyName, + IEnumerable patternsStrings, + bool literalPath) + { + var patterns = patternsStrings + .SelectMany(patternsString => GetSourcesSplit(patternsString)) + .Select(patternString => + patternString.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar)); + + foreach (var pattern in patterns) + { + if (Path.IsPathRooted(pattern)) + { + throw new InvalidOperationException($"The '{propertyName}' property cannot be a rooted path."); + } + + if (literalPath && pattern.Contains('*')) + { + throw new InvalidOperationException($"The '{propertyName}' property cannot contain wildcard characters."); + } + } + + return new List(patterns.Select(pattern => FolderToPattern(pattern, projectDirectory))); + } + + private static IEnumerable GetSourcesSplit(string sourceDescription) + { + if (string.IsNullOrEmpty(sourceDescription)) + { + return Enumerable.Empty(); + } + + return sourceDescription.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries); + } + + private static string FolderToPattern(string candidate, string projectDir) + { + // If it's already a pattern, no change is needed + if (candidate.Contains('*')) + { + return candidate; + } + + // If the given string ends with a path separator, or it is an existing directory + // we convert this folder name to a pattern matching all files in the folder + if (candidate.EndsWith(@"\") || + candidate.EndsWith("/") || + Directory.Exists(Path.Combine(projectDir, candidate))) + { + return Path.Combine(candidate, "**", "*"); + } + + // Otherwise, it represents a single file + return candidate; + } + } +} diff --git a/src/Microsoft.DotNet.ProjectModel/Files/IncludeEntry.cs b/src/Microsoft.DotNet.ProjectModel/Files/IncludeEntry.cs new file mode 100644 index 000000000..e7c8b11a0 --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/Files/IncludeEntry.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Microsoft.Extensions.Internal; + +namespace Microsoft.DotNet.ProjectModel.Files +{ + public class IncludeEntry : IEquatable + { + public string TargetPath { get; } + + public string SourcePath { get; } + + public bool IsCustomTarget { get; set; } + + public IncludeEntry(string target, string source) + { + TargetPath = target; + SourcePath = source; + } + + public override bool Equals(object obj) + { + return Equals((IncludeEntry)obj); + } + + public override int GetHashCode() + { + var combiner = HashCodeCombiner.Start(); + combiner.Add(TargetPath); + combiner.Add(SourcePath); + + return combiner.CombinedHash; + } + + public bool Equals(IncludeEntry other) + { + return other != null && + string.Equals(TargetPath, other.TargetPath, StringComparison.Ordinal) && + string.Equals(SourcePath, other.SourcePath, StringComparison.Ordinal) && + IsCustomTarget == other.IsCustomTarget; + } + } +} diff --git a/src/Microsoft.DotNet.ProjectModel/Files/IncludeFilesResolver.cs b/src/Microsoft.DotNet.ProjectModel/Files/IncludeFilesResolver.cs new file mode 100644 index 000000000..2c63d8f2f --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/Files/IncludeFilesResolver.cs @@ -0,0 +1,182 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.DotNet.ProjectModel.Utilities; +using Microsoft.Extensions.FileSystemGlobbing; + +namespace Microsoft.DotNet.ProjectModel.Files +{ + public class IncludeFilesResolver + { + public static IEnumerable GetIncludeFiles(IncludeContext context, string targetBasePath, IList diagnostics) + { + return GetIncludeFiles(context, targetBasePath, diagnostics, flatten: false); + } + + public static IEnumerable GetIncludeFiles( + IncludeContext context, + string targetBasePath, + IList diagnostics, + bool flatten) + { + var sourceBasePath = PathUtility.EnsureTrailingSlash(context.SourceBasePath); + targetBasePath = PathUtility.GetPathWithDirectorySeparator(targetBasePath); + + var includeEntries = new HashSet(); + + // Check for illegal characters in target path + if (string.IsNullOrEmpty(targetBasePath)) + { + diagnostics?.Add(new DiagnosticMessage( + ErrorCodes.NU1003, + $"Invalid '{context.Option}' section. The target '{targetBasePath}' is invalid, " + + "targets must either be a file name or a directory suffixed with '/'. " + + "The root directory of the package can be specified by using a single '/' character.", + sourceBasePath, + DiagnosticMessageSeverity.Error)); + } + else if (targetBasePath.Split('/').Any(s => s.Equals(".") || s.Equals(".."))) + { + diagnostics?.Add(new DiagnosticMessage( + ErrorCodes.NU1004, + $"Invalid '{context.Option}' section. " + + $"The target '{targetBasePath}' contains path-traversal characters ('.' or '..'). " + + "These characters are not permitted in target paths.", + sourceBasePath, + DiagnosticMessageSeverity.Error)); + } + else + { + var files = GetIncludeFilesCore( + sourceBasePath, + context.IncludePatterns, + context.ExcludePatterns, + context.IncludeFiles, + context.ExcludeFiles, + context.BuiltInsInclude, + context.BuiltInsExclude); + + var isFile = targetBasePath[targetBasePath.Length - 1] != Path.DirectorySeparatorChar; + if (isFile && files.Count() > 1) + { + // It's a file. But the glob matched multiple things + diagnostics?.Add(new DiagnosticMessage( + ErrorCodes.NU1005, + $"Invalid '{ProjectFilesCollection.PackIncludePropertyName}' section. " + + $"The target '{targetBasePath}' refers to a single file, but the corresponding pattern " + + "produces multiple files. To mark the target as a directory, suffix it with '/'.", + sourceBasePath, + DiagnosticMessageSeverity.Error)); + } + else if (isFile && files.Any()) + { + includeEntries.Add(new IncludeEntry(targetBasePath, files.First())); + } + else + { + targetBasePath = targetBasePath.Substring(0, targetBasePath.Length - 1); + + foreach (var file in files) + { + string targetPath = null; + + if (flatten) + { + targetPath = Path.Combine(targetBasePath, Path.GetFileName(file)); + } + else + { + targetPath = Path.Combine(targetBasePath, PathUtility.GetRelativePath(sourceBasePath, file)); + } + + includeEntries.Add(new IncludeEntry(targetPath, file)); + } + } + } + + if (context.Mappings != null) + { + // Finally add all the mappings + foreach (var map in context.Mappings) + { + var targetPath = Path.Combine(targetBasePath, PathUtility.GetPathWithDirectorySeparator(map.Key)); + + foreach (var file in GetIncludeFiles(map.Value, targetPath, diagnostics, flatten)) + { + file.IsCustomTarget = true; + + // Prefer named targets over default ones + includeEntries.RemoveWhere(f => string.Equals(f.SourcePath, file.SourcePath)); + includeEntries.Add(file); + } + } + } + + return includeEntries; + } + + private static IEnumerable GetIncludeFilesCore( + string sourceBasePath, + List includePatterns, + List excludePatterns, + List includeFiles, + List excludeFiles, + List builtInsInclude, + List builtInsExclude) + { + var literalIncludedFiles = new List(); + + if (includeFiles != null) + { + // literal included files are added at the last, but the search happens early + // so as to make the process fail early in case there is missing file. fail early + // helps to avoid unnecessary globing for performance optimization + foreach (var literalRelativePath in includeFiles) + { + var fullPath = Path.GetFullPath(Path.Combine(sourceBasePath, literalRelativePath)); + + if (!File.Exists(fullPath)) + { + throw new InvalidOperationException(string.Format("Can't find file {0}", literalRelativePath)); + } + + literalIncludedFiles.Add(fullPath); + } + } + + // Globbing + var matcher = new Matcher(); + if (builtInsInclude != null) + { + matcher.AddIncludePatterns(builtInsInclude); + } + if (includePatterns != null) + { + matcher.AddIncludePatterns(includePatterns); + } + if (builtInsExclude != null) + { + matcher.AddExcludePatterns(builtInsExclude); + } + if (excludePatterns != null) + { + matcher.AddExcludePatterns(excludePatterns); + } + + var files = matcher.GetResultsInFullPath(sourceBasePath); + files = files.Concat(literalIncludedFiles).Distinct(); + + if (files.Any() && excludeFiles != null) + { + var literalExcludedFiles = excludeFiles.Select(file => Path.GetFullPath(Path.Combine(sourceBasePath, file))); + files = files.Except(literalExcludedFiles); + } + + return files; + } + } +} diff --git a/src/Microsoft.DotNet.ProjectModel/Files/PackIncludeEntry.cs b/src/Microsoft.DotNet.ProjectModel/Files/PackIncludeEntry.cs index f5f4963cb..615c42099 100644 --- a/src/Microsoft.DotNet.ProjectModel/Files/PackIncludeEntry.cs +++ b/src/Microsoft.DotNet.ProjectModel/Files/PackIncludeEntry.cs @@ -39,11 +39,11 @@ namespace Microsoft.DotNet.ProjectModel.Files return new string[] { json.Value() }; } - if(json.Type == JTokenType.Array) + if (json.Type == JTokenType.Array) { return json.Select(v => v.ToString()).ToArray(); } return new string[0]; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/Files/PatternsCollectionHelper.cs b/src/Microsoft.DotNet.ProjectModel/Files/PatternsCollectionHelper.cs index c457388ee..e87a316fb 100644 --- a/src/Microsoft.DotNet.ProjectModel/Files/PatternsCollectionHelper.cs +++ b/src/Microsoft.DotNet.ProjectModel/Files/PatternsCollectionHelper.cs @@ -1,11 +1,10 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; -using System.IO; using System.Linq; +using Newtonsoft.Json.Linq; namespace Microsoft.DotNet.ProjectModel.Files { @@ -27,18 +26,21 @@ namespace Microsoft.DotNet.ProjectModel.Files JToken propertyNameToken; if (!rawProject.TryGetValue(propertyName, out propertyNameToken)) { - return CreateCollection(projectDirectory, propertyName, defaultPatterns, literalPath); + return IncludeContext.CreateCollection( + projectDirectory, propertyName, defaultPatterns, literalPath); } if (propertyNameToken.Type == JTokenType.String) { - return CreateCollection(projectDirectory, propertyName, new string[] { propertyNameToken.Value() }, literalPath); + return IncludeContext.CreateCollection( + projectDirectory, propertyName, new string[] { propertyNameToken.Value() }, literalPath); } if (propertyNameToken.Type == JTokenType.Array) { var valuesInArray = propertyNameToken.Values(); - return CreateCollection(projectDirectory, propertyName, valuesInArray.Select(s => s.ToString()), literalPath); + return IncludeContext.CreateCollection( + projectDirectory, propertyName, valuesInArray.Select(s => s.ToString()), literalPath); } } catch (Exception ex) @@ -48,59 +50,5 @@ namespace Microsoft.DotNet.ProjectModel.Files throw FileFormatException.Create("Value must be either string or array.", rawProject.Value(propertyName), projectFilePath); } - - private static IEnumerable CreateCollection(string projectDirectory, string propertyName, IEnumerable patternsStrings, bool literalPath) - { - var patterns = patternsStrings.SelectMany(patternsString => GetSourcesSplit(patternsString)) - .Select(patternString => patternString.Replace('/', Path.DirectorySeparatorChar).Replace('\\', Path.DirectorySeparatorChar)); - - foreach (var pattern in patterns) - { - if (Path.IsPathRooted(pattern)) - { - throw new InvalidOperationException($"The '{propertyName}' property cannot be a rooted path."); - } - - if (literalPath && pattern.Contains('*')) - { - throw new InvalidOperationException($"The '{propertyName}' property cannot contain wildcard characters."); - } - } - - return new List(patterns.Select(pattern => FolderToPattern(pattern, projectDirectory))); - } - - private static IEnumerable GetSourcesSplit(string sourceDescription) - { - if (string.IsNullOrEmpty(sourceDescription)) - { - return Enumerable.Empty(); - } - - return sourceDescription.Split(PatternSeparator, StringSplitOptions.RemoveEmptyEntries); - } - - private static string FolderToPattern(string candidate, string projectDir) - { - // This conversion is needed to support current template - - // If it's already a pattern, no change is needed - if (candidate.Contains('*')) - { - return candidate; - } - - // If the given string ends with a path separator, or it is an existing directory - // we convert this folder name to a pattern matching all files in the folder - if (candidate.EndsWith(@"\") || - candidate.EndsWith("/") || - Directory.Exists(Path.Combine(projectDir, candidate))) - { - return Path.Combine(candidate, "**", "*"); - } - - // Otherwise, it represents a single file - return candidate; - } } } diff --git a/src/Microsoft.DotNet.ProjectModel/Files/ProjectFilesCollection.cs b/src/Microsoft.DotNet.ProjectModel/Files/ProjectFilesCollection.cs index a6ddc1acb..7cd84d78f 100644 --- a/src/Microsoft.DotNet.ProjectModel/Files/ProjectFilesCollection.cs +++ b/src/Microsoft.DotNet.ProjectModel/Files/ProjectFilesCollection.cs @@ -1,10 +1,8 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Newtonsoft.Json.Linq; namespace Microsoft.DotNet.ProjectModel.Files diff --git a/src/Microsoft.DotNet.ProjectModel/PackOptions.cs b/src/Microsoft.DotNet.ProjectModel/PackOptions.cs new file mode 100644 index 000000000..ab8ee37ff --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/PackOptions.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.DotNet.ProjectModel.Files; + +namespace Microsoft.DotNet.ProjectModel +{ + public class PackOptions + { + public string[] Tags { get; set; } + + public string[] Owners { get; set; } + + public string ReleaseNotes { get; set; } + + public string IconUrl { get; set; } + + public string ProjectUrl { get; set; } + + public string LicenseUrl { get; set; } + + public bool RequireLicenseAcceptance { get; set; } + + public string RepositoryType { get; set; } + + public string RepositoryUrl { get; set; } + + public string Summary { get; set; } + + public IncludeContext PackInclude { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/Project.cs b/src/Microsoft.DotNet.ProjectModel/Project.cs index 9e1c26437..737206c40 100644 --- a/src/Microsoft.DotNet.ProjectModel/Project.cs +++ b/src/Microsoft.DotNet.ProjectModel/Project.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.ProjectModel.Graph; using NuGet.Frameworks; @@ -47,16 +46,10 @@ namespace Microsoft.DotNet.ProjectModel public string Copyright { get; set; } - public string Summary { get; set; } - public string Language { get; set; } - public string ReleaseNotes { get; set; } - public string[] Authors { get; set; } - public string[] Owners { get; set; } - public bool EmbedInteropTypes { get; set; } public NuGetVersion Version { get; set; } @@ -69,28 +62,24 @@ namespace Microsoft.DotNet.ProjectModel public string EntryPoint { get; set; } - public string ProjectUrl { get; set; } - - public string LicenseUrl { get; set; } - - public string IconUrl { get; set; } - - public bool RequireLicenseAcceptance { get; set; } - - public string[] Tags { get; set; } - - public string CompilerName { get; set; } - public string TestRunner { get; set; } public ProjectFilesCollection Files { get; set; } + public PackOptions PackOptions { get; set; } + + public RuntimeOptions RuntimeOptions { get; set; } + public IDictionary Commands { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase); public IDictionary> Scripts { get; } = new Dictionary>(StringComparer.OrdinalIgnoreCase); public string RawRuntimeOptions { get; set; } + public IncludeContext PublishOptions { get; set; } + + public List Diagnostics { get; } = new List(); + public bool IsTestProject => !string.IsNullOrEmpty(TestRunner); public IEnumerable GetTargetFrameworks() @@ -134,10 +123,10 @@ namespace Microsoft.DotNet.ProjectModel public bool HasRuntimeOutput(string configuration) { - var compilationOptions = GetCompilerOptions(targetFramework: null, configurationName: configuration); + var compilerOptions = GetCompilerOptions(targetFramework: null, configurationName: configuration); // TODO: Make this opt in via another mechanism - return compilationOptions.EmitEntryPoint.GetValueOrDefault() || IsTestProject; + return compilerOptions.EmitEntryPoint.GetValueOrDefault() || IsTestProject; } private CommonCompilerOptions GetCompilerOptions() diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs b/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs index bcf41c4d9..691b4991f 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectContextBuilder.cs @@ -337,6 +337,11 @@ namespace Microsoft.DotNet.ProjectModel } } + if (Project != null) + { + diagnostics.AddRange(Project.Diagnostics); + } + // Create a library manager var libraryManager = new LibraryManager(libraries.Values.ToList(), diagnostics, Project?.ProjectFilePath); @@ -567,9 +572,8 @@ namespace Microsoft.DotNet.ProjectModel private Project ResolveProject(string projectDirectory) { - // TODO: Handle diagnostics Project project; - if (ProjectReader.TryGetProject(projectDirectory, out project, diagnostics: null, settings: Settings)) + if (ProjectReader.TryGetProject(projectDirectory, out project, settings: Settings)) { return project; } diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectExtensions.cs b/src/Microsoft.DotNet.ProjectModel/ProjectExtensions.cs index 2c5d9c69c..b981fc9a1 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectExtensions.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectExtensions.cs @@ -21,7 +21,7 @@ namespace Microsoft.DotNet.ProjectModel { foreach (var kvp in _compilerNameToLanguageId) { - if (kvp.Key == project.CompilerName) + if (kvp.Key == (project._defaultCompilerOptions.CompilerName)) { return kvp.Value; } diff --git a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs index c82b58401..50017451e 100644 --- a/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs +++ b/src/Microsoft.DotNet.ProjectModel/ProjectReader.cs @@ -17,7 +17,7 @@ namespace Microsoft.DotNet.ProjectModel { public class ProjectReader { - public static bool TryGetProject(string path, out Project project, ICollection diagnostics = null, ProjectReaderSettings settings = null) + public static bool TryGetProject(string path, out Project project, ProjectReaderSettings settings = null) { project = null; @@ -51,7 +51,7 @@ namespace Microsoft.DotNet.ProjectModel using (var stream = File.OpenRead(projectPath)) { var reader = new ProjectReader(); - project = reader.ReadProject(stream, projectName, projectPath, diagnostics, settings); + project = reader.ReadProject(stream, projectName, projectPath, settings); } } catch (Exception ex) @@ -62,9 +62,7 @@ namespace Microsoft.DotNet.ProjectModel return true; } - public static Project GetProject(string projectPath, ProjectReaderSettings settings = null) => GetProject(projectPath, new List(), settings); - - public static Project GetProject(string projectPath, ICollection diagnostics, ProjectReaderSettings settings = null) + public static Project GetProject(string projectPath, ProjectReaderSettings settings = null) { if (!projectPath.EndsWith(Project.FileName)) { @@ -75,11 +73,11 @@ namespace Microsoft.DotNet.ProjectModel using (var stream = new FileStream(projectPath, FileMode.Open, FileAccess.Read, FileShare.Read)) { - return new ProjectReader().ReadProject(stream, name, projectPath, diagnostics, settings); + return new ProjectReader().ReadProject(stream, name, projectPath, settings); } } - public Project ReadProject(Stream stream, string projectName, string projectPath, ICollection diagnostics, ProjectReaderSettings settings = null) + public Project ReadProject(Stream stream, string projectName, string projectPath, ProjectReaderSettings settings = null) { settings = settings ?? new ProjectReaderSettings(); var project = new Project(); @@ -146,25 +144,13 @@ namespace Microsoft.DotNet.ProjectModel } project.Description = rawProject.Value("description"); - project.Summary = rawProject.Value("summary"); project.Copyright = rawProject.Value("copyright"); project.Title = rawProject.Value("title"); project.EntryPoint = rawProject.Value("entryPoint"); - project.ProjectUrl = rawProject.Value("projectUrl"); - project.LicenseUrl = rawProject.Value("licenseUrl"); - project.IconUrl = rawProject.Value("iconUrl"); - project.CompilerName = rawProject.Value("compilerName") ?? "csc"; project.TestRunner = rawProject.Value("testRunner"); - project.Authors = rawProject.Value("authors")?.Values().ToArray() ?? EmptyArray.Value; - project.Owners = rawProject.Value("owners")?.Values().ToArray() ?? EmptyArray.Value; - project.Tags = rawProject.Value("tags")?.Values().ToArray() ?? EmptyArray.Value; - project.Language = rawProject.Value("language"); - project.ReleaseNotes = rawProject.Value("releaseNotes"); - - project.RequireLicenseAcceptance = rawProject.Value("requireLicenseAcceptance"); // REVIEW: Move this to the dependencies node? project.EmbedInteropTypes = rawProject.Value("embedInteropTypes"); @@ -215,7 +201,11 @@ namespace Microsoft.DotNet.ProjectModel } } - BuildTargetFrameworksAndConfigurations(project, rawProject, diagnostics); + project.PackOptions = GetPackOptions(rawProject, project) ?? new PackOptions(); + project.RuntimeOptions = GetRuntimeOptions(rawProject) ?? new RuntimeOptions(); + project.PublishOptions = GetPublishInclude(rawProject, project); + + BuildTargetFrameworksAndConfigurations(project, rawProject); PopulateDependencies( project.ProjectFilePath, @@ -351,11 +341,11 @@ namespace Microsoft.DotNet.ProjectModel } } - private void BuildTargetFrameworksAndConfigurations(Project project, JObject projectJsonObject, ICollection diagnostics) + private void BuildTargetFrameworksAndConfigurations(Project project, JObject projectJsonObject) { // Get the shared compilationOptions project._defaultCompilerOptions = - GetCompilationOptions(projectJsonObject, project) ?? new CommonCompilerOptions(); + GetCompilationOptions(projectJsonObject, project) ?? new CommonCompilerOptions { CompilerName = "csc" }; project._defaultTargetFrameworkConfiguration = new TargetFrameworkInformation { @@ -423,7 +413,7 @@ namespace Microsoft.DotNet.ProjectModel if (!success) { var lineInfo = (IJsonLineInfo)framework.Value; - diagnostics?.Add( + project.Diagnostics?.Add( new DiagnosticMessage( ErrorCodes.NU1008, $"\"{framework.Key}\" is an unsupported framework.", @@ -518,10 +508,42 @@ namespace Microsoft.DotNet.ProjectModel private static CommonCompilerOptions GetCompilationOptions(JObject rawObject, Project project) { - var rawOptions = rawObject.Value("compilationOptions") as JObject; + var compilerName = rawObject.Value("compilerName"); + if (compilerName != null) + { + var lineInfo = rawObject.Value("compilerName"); + project.Diagnostics?.Add( + new DiagnosticMessage( + ErrorCodes.DOTNET1016, + $"The 'compilerName' option in the root is deprecated. Use it in 'buildOptions' instead.", + project.ProjectFilePath, + DiagnosticMessageSeverity.Warning, + lineInfo.LineNumber, + lineInfo.LinePosition)); + } + + var rawOptions = rawObject.Value("buildOptions") as JObject; if (rawOptions == null) { - return null; + rawOptions = rawObject.Value("compilationOptions") as JObject; + if (rawOptions == null) + { + return new CommonCompilerOptions + { + CompilerName = compilerName ?? "csc" + }; + } + + var lineInfo = (IJsonLineInfo)rawOptions; + + project.Diagnostics?.Add( + new DiagnosticMessage( + ErrorCodes.DOTNET1015, + $"The 'compilationOptions' option is deprecated. Use 'buildOptions' instead.", + project.ProjectFilePath, + DiagnosticMessageSeverity.Warning, + lineInfo.LineNumber, + lineInfo.LinePosition)); } var analyzerOptionsJson = rawOptions.Value("analyzerOptions") as JObject; @@ -571,10 +593,148 @@ namespace Microsoft.DotNet.ProjectModel EmitEntryPoint = rawOptions.Value("emitEntryPoint"), GenerateXmlDocumentation = rawOptions.Value("xmlDoc"), PreserveCompilationContext = rawOptions.Value("preserveCompilationContext"), - OutputName = rawOptions.Value("outputName") + OutputName = rawOptions.Value("outputName"), + CompilerName = rawOptions.Value("compilerName") ?? compilerName ?? "csc", + CompileInclude = GetIncludeContext( + project, + rawOptions, + "compile", + defaultBuiltInInclude: ProjectFilesCollection.DefaultCompileBuiltInPatterns, + defaultBuiltInExclude: ProjectFilesCollection.DefaultBuiltInExcludePatterns), + EmbedInclude = GetIncludeContext( + project, + rawOptions, + "embed", + defaultBuiltInInclude: ProjectFilesCollection.DefaultResourcesBuiltInPatterns, + defaultBuiltInExclude: ProjectFilesCollection.DefaultBuiltInExcludePatterns), + CopyToOutputInclude = GetIncludeContext( + project, + rawOptions, + "copyToOutput", + defaultBuiltInInclude: null, + defaultBuiltInExclude: ProjectFilesCollection.DefaultPublishExcludePatterns) }; } + private static IncludeContext GetIncludeContext( + Project project, + JObject rawOptions, + string option, + string[] defaultBuiltInInclude, + string[] defaultBuiltInExclude) + { + var contextOption = rawOptions.Value(option); + if (contextOption != null) + { + return new IncludeContext( + project.ProjectDirectory, + option, + rawOptions, + defaultBuiltInInclude, + defaultBuiltInExclude); + } + + return null; + } + + private static PackOptions GetPackOptions(JObject rawProject, Project project) + { + var rawPackOptions = rawProject.Value("packOptions") as JObject; + + // Files to be packed along with the project + IncludeContext packInclude = null; + if (rawPackOptions != null && rawPackOptions.Value("files") != null) + { + packInclude = new IncludeContext( + project.ProjectDirectory, + "files", + rawPackOptions, + defaultBuiltInInclude: null, + defaultBuiltInExclude: ProjectFilesCollection.DefaultBuiltInExcludePatterns); + } + + var repository = GetPackOptionsValue("repository", rawProject, rawPackOptions, project) as JObject; + + return new PackOptions + { + ProjectUrl = GetPackOptionsValue("projectUrl", rawProject, rawPackOptions, project), + LicenseUrl = GetPackOptionsValue("licenseUrl", rawProject, rawPackOptions, project), + IconUrl = GetPackOptionsValue("iconUrl", rawProject, rawPackOptions, project), + Owners = GetPackOptionsValue("owners", rawProject, rawPackOptions, project)?.Values().ToArray() ?? EmptyArray.Value, + Tags = GetPackOptionsValue("tags", rawProject, rawPackOptions, project)?.Values().ToArray() ?? EmptyArray.Value, + ReleaseNotes = GetPackOptionsValue("releaseNotes", rawProject, rawPackOptions, project), + RequireLicenseAcceptance = GetPackOptionsValue("requireLicenseAcceptance", rawProject, rawPackOptions, project), + Summary = GetPackOptionsValue("summary", rawProject, rawPackOptions, project), + RepositoryType = repository?.Value("type"), + RepositoryUrl = repository?.Value("url"), + PackInclude = packInclude + }; + } + + private static T GetPackOptionsValue( + string option, + JObject rawProject, + JObject rawPackOptions, + Project project) + { + var rootValue = rawProject.Value(option); + if (rawProject.GetValue(option) != null) + { + var lineInfo = rawProject.Value(option); + project.Diagnostics?.Add( + new DiagnosticMessage( + ErrorCodes.DOTNET1016, + $"The '{option}' option in the root is deprecated. Use it in 'packOptions' instead.", + project.ProjectFilePath, + DiagnosticMessageSeverity.Warning, + lineInfo.LineNumber, + lineInfo.LinePosition)); + } + + if (rawPackOptions != null) + { + var packOptionValue = rawPackOptions.Value(option); + if (packOptionValue != null) + { + return packOptionValue; + } + } + + return rootValue; + } + + private static RuntimeOptions GetRuntimeOptions(JObject rawProject) + { + var rawRuntimeOptions = rawProject.Value("runtimeOptions") as JObject; + if (rawRuntimeOptions == null) + { + return null; + } + + return new RuntimeOptions + { + // Value(null) will return default(T) which is false in this case. + GcServer = rawRuntimeOptions.Value("gcServer"), + GcConcurrent = rawRuntimeOptions.Value("gcConcurrent") + }; + } + + private static IncludeContext GetPublishInclude(JObject rawProject, Project project) + { + var rawPublishOptions = rawProject.Value("publishOptions"); + if (rawPublishOptions != null) + { + return new IncludeContext( + project.ProjectDirectory, + "publishOptions", + rawProject, + defaultBuiltInInclude: null, + defaultBuiltInExclude: ProjectFilesCollection.DefaultPublishExcludePatterns); + } + + return null; + } + private static string MakeDefaultTargetFrameworkDefine(NuGetFramework targetFramework) { var shortName = targetFramework.GetTwoDigitShortFolderName(); diff --git a/src/Microsoft.DotNet.ProjectModel/RuntimeOptions.cs b/src/Microsoft.DotNet.ProjectModel/RuntimeOptions.cs new file mode 100644 index 000000000..b11f6c37d --- /dev/null +++ b/src/Microsoft.DotNet.ProjectModel/RuntimeOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.DotNet.ProjectModel +{ + public class RuntimeOptions + { + public bool GcServer { get; set; } + + public bool GcConcurrent { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs b/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs index 82b65e3be..ea420762f 100644 --- a/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs +++ b/src/Microsoft.DotNet.ProjectModel/RuntimeOutputFiles.cs @@ -38,9 +38,9 @@ namespace Microsoft.DotNet.ProjectModel extension = FileNameSuffixes.DotNet.DynamicLib; } - var compilationOptions = Project.GetCompilerOptions(Framework, Configuration); + var compilerOptions = Project.GetCompilerOptions(Framework, Configuration); - return Path.Combine(BasePath, compilationOptions.OutputName + extension); + return Path.Combine(BasePath, compilerOptions.OutputName + extension); } } diff --git a/src/Microsoft.DotNet.ProjectModel/WorkspaceContext.cs b/src/Microsoft.DotNet.ProjectModel/WorkspaceContext.cs index dc6c4812c..ac225eac6 100644 --- a/src/Microsoft.DotNet.ProjectModel/WorkspaceContext.cs +++ b/src/Microsoft.DotNet.ProjectModel/WorkspaceContext.cs @@ -284,13 +284,14 @@ namespace Microsoft.DotNet.ProjectModel if (currentEntry.IsInvalid) { Project project; - if (!ProjectReader.TryGetProject(projectDirectory, out project, currentEntry.Diagnostics, _settings)) + if (!ProjectReader.TryGetProject(projectDirectory, out project, _settings)) { currentEntry.Reset(); } else { currentEntry.Model = project; + currentEntry.Diagnostics.AddRange(project.Diagnostics); currentEntry.FilePath = project.ProjectFilePath; currentEntry.UpdateLastWriteTimeUtc(); } diff --git a/src/dotnet/commands/dotnet-build/CompilerIOManager.cs b/src/dotnet/commands/dotnet-build/CompilerIOManager.cs index ed2b52843..387f84500 100644 --- a/src/dotnet/commands/dotnet-build/CompilerIOManager.cs +++ b/src/dotnet/commands/dotnet-build/CompilerIOManager.cs @@ -54,6 +54,7 @@ namespace Microsoft.DotNet.Tools.Build var calculator = project.GetOutputPaths(_configuration, _buildBasePath, _outputPath); var binariesOutputPath = calculator.CompilationOutputPath; + var compilerOptions = project.ProjectFile.GetCompilerOptions(project.TargetFramework, _configuration); // input: project.json inputs.Add(project.ProjectFile.ProjectFilePath); @@ -62,7 +63,7 @@ namespace Microsoft.DotNet.Tools.Build AddLockFile(project, inputs); // input: source files - inputs.AddRange(CompilerUtil.GetCompilationSources(project)); + inputs.AddRange(CompilerUtil.GetCompilationSources(project, compilerOptions)); var allOutputPath = new HashSet(calculator.CompilationFiles.All()); if (isRootProject && project.ProjectFile.HasRuntimeOutput(_configuration)) @@ -84,10 +85,10 @@ namespace Microsoft.DotNet.Tools.Build AddCompilationOptions(project, _configuration, inputs); // input / output: resources with culture - AddNonCultureResources(project, calculator.IntermediateOutputDirectoryPath, inputs, outputs); + AddNonCultureResources(project, calculator.IntermediateOutputDirectoryPath, inputs, outputs, compilerOptions); // input / output: resources without culture - AddCultureResources(project, binariesOutputPath, inputs, outputs); + AddCultureResources(project, binariesOutputPath, inputs, outputs, compilerOptions); return new CompilerIO(inputs, outputs); } @@ -121,9 +122,24 @@ namespace Microsoft.DotNet.Tools.Build } } - private static void AddNonCultureResources(ProjectContext project, string intermediaryOutputPath, List inputs, IList outputs) + private static void AddNonCultureResources( + ProjectContext project, + string intermediaryOutputPath, + List inputs, + IList outputs, + CommonCompilerOptions compilationOptions) { - foreach (var resourceIO in CompilerUtil.GetNonCultureResources(project.ProjectFile, intermediaryOutputPath)) + List resources = null; + if (compilationOptions.EmbedInclude == null) + { + resources = CompilerUtil.GetNonCultureResources(project.ProjectFile, intermediaryOutputPath); + } + else + { + resources = CompilerUtil.GetNonCultureResourcesFromIncludeEntries(project.ProjectFile, intermediaryOutputPath, compilationOptions); + } + + foreach (var resourceIO in resources) { inputs.Add(resourceIO.InputFile); @@ -134,9 +150,24 @@ namespace Microsoft.DotNet.Tools.Build } } - private static void AddCultureResources(ProjectContext project, string outputPath, List inputs, List outputs) + private static void AddCultureResources( + ProjectContext project, + string outputPath, + List inputs, + List outputs, + CommonCompilerOptions compilationOptions) { - foreach (var cultureResourceIO in CompilerUtil.GetCultureResources(project.ProjectFile, outputPath)) + List resources = null; + if (compilationOptions.EmbedInclude == null) + { + resources = CompilerUtil.GetCultureResources(project.ProjectFile, outputPath); + } + else + { + resources = CompilerUtil.GetCultureResourcesFromIncludeEntries(project.ProjectFile, outputPath, compilationOptions); + } + + foreach (var cultureResourceIO in resources) { inputs.AddRange(cultureResourceIO.InputFileToMetadata.Keys); diff --git a/src/dotnet/commands/dotnet-build/IncrementalPreconditionManager.cs b/src/dotnet/commands/dotnet-build/IncrementalPreconditionManager.cs index b57fa933a..c63ad77e2 100644 --- a/src/dotnet/commands/dotnet-build/IncrementalPreconditionManager.cs +++ b/src/dotnet/commands/dotnet-build/IncrementalPreconditionManager.cs @@ -68,7 +68,8 @@ namespace Microsoft.DotNet.Tools.Build { if (project.ProjectFile != null) { - var projectCompiler = project.ProjectFile.CompilerName; + var compilerOptions = project.ProjectFile.GetCompilerOptions(project.TargetFramework, null); + var projectCompiler = compilerOptions.CompilerName; if (!KnownCompilers.Any(knownCompiler => knownCompiler.Equals(projectCompiler, StringComparison.Ordinal))) { diff --git a/src/dotnet/commands/dotnet-build/ProjectBuilder.cs b/src/dotnet/commands/dotnet-build/ProjectBuilder.cs index 663c609b1..56bdbd2c9 100644 --- a/src/dotnet/commands/dotnet-build/ProjectBuilder.cs +++ b/src/dotnet/commands/dotnet-build/ProjectBuilder.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Files; using NuGet.Frameworks; namespace Microsoft.DotNet.Tools.Build @@ -67,7 +68,7 @@ namespace Microsoft.DotNet.Tools.Build } var context = projectNode.ProjectContext; - if (!context.ProjectFile.Files.SourceFiles.Any()) + if (!HasSourceFiles(context)) { return CompilationResult.IncrementalSkip; } @@ -82,5 +83,19 @@ namespace Microsoft.DotNet.Tools.Build return CompilationResult.IncrementalSkip; } } + + private static bool HasSourceFiles(ProjectContext context) + { + var compilerOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, null); + + if (compilerOptions.CompileInclude == null) + { + return context.ProjectFile.Files.SourceFiles.Any(); + } + + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null); + + return includeFiles.Any(); + } } } \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-compile/Compiler.cs b/src/dotnet/commands/dotnet-compile/Compiler.cs index e13457b74..f5cb7c121 100644 --- a/src/dotnet/commands/dotnet-compile/Compiler.cs +++ b/src/dotnet/commands/dotnet-compile/Compiler.cs @@ -46,9 +46,21 @@ namespace Microsoft.DotNet.Tools.Compiler return success; } - protected static bool AddNonCultureResources(Project project, List compilerArgs, string intermediateOutputPath) + protected static bool AddNonCultureResources( + Project project, + List compilerArgs, + string intermediateOutputPath, + CommonCompilerOptions compilationOptions) { - var resgenFiles = CompilerUtil.GetNonCultureResources(project, intermediateOutputPath); + List resgenFiles = null; + if (compilationOptions.EmbedInclude == null) + { + resgenFiles = CompilerUtil.GetNonCultureResources(project, intermediateOutputPath); + } + else + { + resgenFiles = CompilerUtil.GetNonCultureResourcesFromIncludeEntries(project, intermediateOutputPath, compilationOptions); + } foreach (var resgenFile in resgenFiles) { @@ -80,10 +92,20 @@ namespace Microsoft.DotNet.Tools.Compiler protected static bool GenerateCultureResourceAssemblies( Project project, List dependencies, - string outputPath) + string outputPath, + CommonCompilerOptions compilationOptions) { var referencePaths = CompilerUtil.GetReferencePathsForCultureResgen(dependencies); - var cultureResgenFiles = CompilerUtil.GetCultureResources(project, outputPath); + + List cultureResgenFiles = null; + if (compilationOptions.EmbedInclude == null) + { + cultureResgenFiles = CompilerUtil.GetCultureResources(project, outputPath); + } + else + { + cultureResgenFiles = CompilerUtil.GetCultureResourcesFromIncludeEntries(project, outputPath, compilationOptions); + } foreach (var resgenFile in cultureResgenFiles) { diff --git a/src/dotnet/commands/dotnet-compile/CompilerUtil.cs b/src/dotnet/commands/dotnet-compile/CompilerUtil.cs index f258dc43c..0117062c8 100644 --- a/src/dotnet/commands/dotnet-compile/CompilerUtil.cs +++ b/src/dotnet/commands/dotnet-compile/CompilerUtil.cs @@ -9,6 +9,7 @@ using System.Linq; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.Cli.Compiler.Common; using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.ProjectModel.Resources; using Microsoft.DotNet.Tools.Common; @@ -49,11 +50,29 @@ namespace Microsoft.DotNet.Tools.Compiler // used in incremental compilation public static List GetNonCultureResources(Project project, string intermediateOutputPath) { - return + return (from resourceFile in project.Files.ResourceFiles - let inputFile = resourceFile.Key + let inputFile = resourceFile.Key + where string.IsNullOrEmpty(ResourceUtility.GetResourceCultureName(inputFile)) + let metadataName = GetResourceFileMetadataName(project, resourceFile.Key, resourceFile.Value) + let outputFile = ResourceUtility.IsResxFile(inputFile) ? Path.Combine(intermediateOutputPath, metadataName) : null + select new NonCultureResgenIO(inputFile, outputFile, metadataName) + ).ToList(); + } + + // used in incremental compilation + public static List GetNonCultureResourcesFromIncludeEntries( + Project project, + string intermediateOutputPath, + CommonCompilerOptions compilationOptions) + { + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilationOptions.EmbedInclude, "/", diagnostics: null); + return + (from resourceFile in includeFiles + let inputFile = resourceFile.SourcePath where string.IsNullOrEmpty(ResourceUtility.GetResourceCultureName(inputFile)) - let metadataName = GetResourceFileMetadataName(project, resourceFile) + let target = resourceFile.IsCustomTarget ? resourceFile.TargetPath : null + let metadataName = GetResourceFileMetadataName(project, resourceFile.SourcePath, target) let outputFile = ResourceUtility.IsResxFile(inputFile) ? Path.Combine(intermediateOutputPath, metadataName) : null select new NonCultureResgenIO(inputFile, outputFile, metadataName) ).ToList(); @@ -78,9 +97,29 @@ namespace Microsoft.DotNet.Tools.Compiler { return (from resourceFileGroup in project.Files.ResourceFiles.GroupBy(resourceFile => ResourceUtility.GetResourceCultureName(resourceFile.Key)) + let culture = resourceFileGroup.Key + where !string.IsNullOrEmpty(culture) + let inputFileToMetadata = resourceFileGroup.ToDictionary(r => r.Key, r => GetResourceFileMetadataName(project, r.Key, r.Value)) + let resourceOutputPath = Path.Combine(outputPath, culture) + let outputFile = Path.Combine(resourceOutputPath, project.Name + ".resources.dll") + select new CultureResgenIO(culture, inputFileToMetadata, outputFile) + ).ToList(); + } + + // used in incremental compilation + public static List GetCultureResourcesFromIncludeEntries( + Project project, + string outputPath, + CommonCompilerOptions compilationOptions) + { + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilationOptions.EmbedInclude, "/", diagnostics: null); + return + (from resourceFileGroup in includeFiles + .GroupBy(resourceFile => ResourceUtility.GetResourceCultureName(resourceFile.SourcePath)) let culture = resourceFileGroup.Key where !string.IsNullOrEmpty(culture) - let inputFileToMetadata = resourceFileGroup.ToDictionary(r => r.Key, r => GetResourceFileMetadataName(project, r)) + let inputFileToMetadata = resourceFileGroup.ToDictionary( + r => r.SourcePath, r => GetResourceFileMetadataName(project, r.SourcePath, r.IsCustomTarget ? r.TargetPath : null)) let resourceOutputPath = Path.Combine(outputPath, culture) let outputFile = Path.Combine(resourceOutputPath, project.Name + ".resources.dll") select new CultureResgenIO(culture, inputFileToMetadata, outputFile) @@ -93,14 +132,14 @@ namespace Microsoft.DotNet.Tools.Compiler return dependencies.SelectMany(libraryExport => libraryExport.CompilationAssemblies).Select(r => r.ResolvedPath).ToList(); } - public static string GetResourceFileMetadataName(Project project, KeyValuePair resourceFile) + public static string GetResourceFileMetadataName(Project project, string resourceFileSource, string resourceFileTarget) { string resourceName = null; string rootNamespace = null; string root = PathUtility.EnsureTrailingSlash(project.ProjectDirectory); - string resourcePath = resourceFile.Key; - if (string.IsNullOrEmpty(resourceFile.Value)) + string resourcePath = resourceFileSource; + if (string.IsNullOrEmpty(resourceFileTarget)) { // No logical name, so use the file name resourceName = ResourceUtility.GetResourceName(root, resourcePath); @@ -108,7 +147,7 @@ namespace Microsoft.DotNet.Tools.Compiler } else { - resourceName = ResourceManifestName.EnsureResourceExtension(resourceFile.Value, resourcePath); + resourceName = ResourceManifestName.EnsureResourceExtension(resourceFileTarget, resourcePath); rootNamespace = null; } @@ -117,12 +156,23 @@ namespace Microsoft.DotNet.Tools.Compiler } // used in incremental compilation - public static IEnumerable GetCompilationSources(ProjectContext project) => project.ProjectFile.Files.SourceFiles; + public static IEnumerable GetCompilationSources(ProjectContext project, CommonCompilerOptions compilerOptions) + { + if (compilerOptions.CompileInclude == null) + { + return project.ProjectFile.Files.SourceFiles; + } + + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null); + + return includeFiles.Select(f => f.SourcePath); + } //used in incremental precondition checks public static IEnumerable GetCommandsInvokedByCompile(ProjectContext project) { - return new List {project.ProjectFile?.CompilerName, "compile"}; + var compilerOptions = project.ProjectFile.GetCompilerOptions(project.TargetFramework, configurationName: null); + return new List { compilerOptions.CompilerName, "compile" }; } } } \ No newline at end of file diff --git a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs index 70a2d4f9d..5ff5c31a5 100644 --- a/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs +++ b/src/dotnet/commands/dotnet-compile/ManagedCompiler.cs @@ -143,15 +143,15 @@ namespace Microsoft.DotNet.Tools.Compiler compilerArgs.Add($"--resource:\"{depsJsonFile}\",{compilationOptions.OutputName}.deps.json"); } - if (!AddNonCultureResources(context.ProjectFile, compilerArgs, intermediateOutputPath)) + if (!AddNonCultureResources(context.ProjectFile, compilerArgs, intermediateOutputPath, compilationOptions)) { return false; } // Add project source files - var sourceFiles = CompilerUtil.GetCompilationSources(context); + var sourceFiles = CompilerUtil.GetCompilationSources(context, compilationOptions); compilerArgs.AddRange(sourceFiles); - var compilerName = context.ProjectFile.CompilerName; + var compilerName = compilationOptions.CompilerName; // Write RSP file var rsp = Path.Combine(intermediateOutputPath, $"dotnet-compile.rsp"); @@ -207,7 +207,7 @@ namespace Microsoft.DotNet.Tools.Compiler if (success) { - success &= GenerateCultureResourceAssemblies(context.ProjectFile, dependencies, outputPath); + success &= GenerateCultureResourceAssemblies(context.ProjectFile, dependencies, outputPath, compilationOptions); } return PrintSummary(diagnostics, sw, success); diff --git a/src/dotnet/commands/dotnet-pack/BuildProjectCommand.cs b/src/dotnet/commands/dotnet-pack/BuildProjectCommand.cs index ccdfb54b2..3492bcda7 100644 --- a/src/dotnet/commands/dotnet-pack/BuildProjectCommand.cs +++ b/src/dotnet/commands/dotnet-pack/BuildProjectCommand.cs @@ -1,11 +1,10 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System.Linq; -using System.Text; -using Microsoft.DotNet.Cli.Utils; -using Microsoft.DotNet.ProjectModel; using System.Collections.Generic; +using System.Linq; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Files; namespace Microsoft.DotNet.Tools.Pack { @@ -32,7 +31,7 @@ namespace Microsoft.DotNet.Tools.Pack public int Execute() { - if (_project.Files.SourceFiles.Any()) + if (HasSourceFiles()) { var argsBuilder = new List(); argsBuilder.Add("--configuration"); @@ -59,5 +58,20 @@ namespace Microsoft.DotNet.Tools.Pack return 0; } + + private bool HasSourceFiles() + { + var compilerOptions = _project.GetCompilerOptions( + _project.GetTargetFramework(targetFramework: null).FrameworkName, _configuration); + + if (compilerOptions.CompileInclude == null) + { + return _project.Files.SourceFiles.Any(); + } + + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null); + + return includeFiles.Any(); + } } } diff --git a/src/dotnet/commands/dotnet-pack/PackageGenerator.cs b/src/dotnet/commands/dotnet-pack/PackageGenerator.cs index c20219ac7..50c236a49 100644 --- a/src/dotnet/commands/dotnet-pack/PackageGenerator.cs +++ b/src/dotnet/commands/dotnet-pack/PackageGenerator.cs @@ -9,16 +9,15 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.ProjectModel.Graph; +using Microsoft.DotNet.ProjectModel.Resources; using Microsoft.DotNet.ProjectModel.Utilities; +using Microsoft.DotNet.Tools.Pack; using Microsoft.Extensions.FileSystemGlobbing; using Microsoft.Extensions.FileSystemGlobbing.Abstractions; using NuGet; using NuGet.Frameworks; using NuGet.Packaging.Core; using NuGet.Versioning; -using Microsoft.DotNet.Cli.Compiler.Common; -using Microsoft.DotNet.ProjectModel.Resources; -using Microsoft.DotNet.Tools.Pack; using PackageBuilder = NuGet.PackageBuilder; namespace Microsoft.DotNet.Tools.Compiler @@ -42,7 +41,6 @@ namespace Microsoft.DotNet.Tools.Compiler public bool BuildPackage(IEnumerable contexts, List packDiagnostics) { - Reporter.Output.WriteLine($"Producing nuget package \"{GetPackageName()}\" for {Project.Name}"); PackageBuilder = CreatePackageBuilder(Project); @@ -76,15 +74,26 @@ namespace Microsoft.DotNet.Tools.Compiler var inputFolder = ArtifactPathsCalculator.InputPathForContext(context); - var compilationOptions = Project.GetCompilerOptions(context.TargetFramework, Configuration); - var outputName = compilationOptions.OutputName; + var compilerOptions = Project.GetCompilerOptions(context.TargetFramework, Configuration); + var outputName = compilerOptions.OutputName; var outputExtension = - context.TargetFramework.IsDesktop() && compilationOptions.EmitEntryPoint.GetValueOrDefault() + context.TargetFramework.IsDesktop() && compilerOptions.EmitEntryPoint.GetValueOrDefault() ? ".exe" : ".dll"; - var resourceCultures = context.ProjectFile.Files.ResourceFiles + IEnumerable resourceCultures = null; + if (compilerOptions.EmbedInclude == null) + { + resourceCultures = context.ProjectFile.Files.ResourceFiles .Select(resourceFile => ResourceUtility.GetResourceCultureName(resourceFile.Key)) .Distinct(); + } + else + { + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.EmbedInclude, "/", diagnostics: null); + resourceCultures = includeFiles + .Select(file => ResourceUtility.GetResourceCultureName(file.SourcePath)) + .Distinct(); + } foreach (var culture in resourceCultures) { @@ -112,7 +121,12 @@ namespace Microsoft.DotNet.Tools.Compiler PackageBuilder.Files.Add(file); } - if (Project.Files.PackInclude != null && Project.Files.PackInclude.Any()) + if (Project.PackOptions.PackInclude != null) + { + var files = IncludeFilesResolver.GetIncludeFiles(Project.PackOptions.PackInclude, "/", diagnostics: packDiagnostics, flatten: true); + PackageBuilder.Files.AddRange(GetPackageFiles(files, packDiagnostics)); + } + else if (Project.Files.PackInclude != null && Project.Files.PackInclude.Any()) { AddPackageFiles(Project.Files.PackInclude, packDiagnostics); } @@ -237,6 +251,20 @@ namespace Microsoft.DotNet.Tools.Compiler } } + private static IEnumerable GetPackageFiles( + IEnumerable includeFiles, + IList diagnostics) + { + foreach (var entry in includeFiles) + { + yield return new PhysicalPackageFile() + { + SourcePath = PathUtility.GetPathWithDirectorySeparator(entry.SourcePath), + TargetPath = PathUtility.GetPathWithDirectorySeparator(entry.TargetPath) + }; + } + } + protected void TryAddOutputFile(ProjectContext context, string outputPath, string filePath) @@ -333,7 +361,7 @@ namespace Microsoft.DotNet.Tools.Compiler { var builder = new PackageBuilder(); builder.Authors.AddRange(project.Authors); - builder.Owners.AddRange(project.Owners); + builder.Owners.AddRange(project.PackOptions.Owners); if (builder.Authors.Count == 0) { @@ -352,26 +380,26 @@ namespace Microsoft.DotNet.Tools.Compiler builder.Id = project.Name; builder.Version = project.Version; builder.Title = project.Title; - builder.Summary = project.Summary; + builder.Summary = project.PackOptions.Summary; builder.Copyright = project.Copyright; - builder.RequireLicenseAcceptance = project.RequireLicenseAcceptance; - builder.ReleaseNotes = project.ReleaseNotes; + builder.RequireLicenseAcceptance = project.PackOptions.RequireLicenseAcceptance; + builder.ReleaseNotes = project.PackOptions.ReleaseNotes; builder.Language = project.Language; - builder.Tags.AddRange(project.Tags); + builder.Tags.AddRange(project.PackOptions.Tags); - if (!string.IsNullOrEmpty(project.IconUrl)) + if (!string.IsNullOrEmpty(project.PackOptions.IconUrl)) { - builder.IconUrl = new Uri(project.IconUrl); + builder.IconUrl = new Uri(project.PackOptions.IconUrl); } - if (!string.IsNullOrEmpty(project.ProjectUrl)) + if (!string.IsNullOrEmpty(project.PackOptions.ProjectUrl)) { - builder.ProjectUrl = new Uri(project.ProjectUrl); + builder.ProjectUrl = new Uri(project.PackOptions.ProjectUrl); } - if (!string.IsNullOrEmpty(project.LicenseUrl)) + if (!string.IsNullOrEmpty(project.PackOptions.LicenseUrl)) { - builder.LicenseUrl = new Uri(project.LicenseUrl); + builder.LicenseUrl = new Uri(project.PackOptions.LicenseUrl); } return builder; diff --git a/src/dotnet/commands/dotnet-pack/SymbolPackageGenerator.cs b/src/dotnet/commands/dotnet-pack/SymbolPackageGenerator.cs index 528297da1..97619ac04 100644 --- a/src/dotnet/commands/dotnet-pack/SymbolPackageGenerator.cs +++ b/src/dotnet/commands/dotnet-pack/SymbolPackageGenerator.cs @@ -1,11 +1,12 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using Microsoft.DotNet.ProjectModel; -using NuGet; using System.Collections.Generic; using System.IO; +using Microsoft.DotNet.ProjectModel; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.Tools.Pack; +using NuGet; namespace Microsoft.DotNet.Tools.Compiler { @@ -34,16 +35,37 @@ namespace Microsoft.DotNet.Tools.Compiler protected override bool GeneratePackage(string nupkg, List packDiagnostics) { - foreach (var path in Project.Files.SourceFiles) - { - var srcFile = new PhysicalPackageFile - { - SourcePath = path, - TargetPath = Path.Combine("src", Common.PathUtility.GetRelativePath(Project.ProjectDirectory, path)) - }; + var compilerOptions = Project.GetCompilerOptions( + Project.GetTargetFramework(targetFramework: null).FrameworkName, Configuration); - PackageBuilder.Files.Add(srcFile); + if (compilerOptions.CompileInclude == null) + { + foreach (var path in Project.Files.SourceFiles) + { + var srcFile = new PhysicalPackageFile + { + SourcePath = path, + TargetPath = Path.Combine("src", Common.PathUtility.GetRelativePath(Project.ProjectDirectory, path)) + }; + + PackageBuilder.Files.Add(srcFile); + } } + else + { + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null); + foreach (var entry in includeFiles) + { + var srcFile = new PhysicalPackageFile + { + SourcePath = entry.SourcePath, + TargetPath = Path.Combine("src", entry.TargetPath) + }; + + PackageBuilder.Files.Add(srcFile); + } + } + return base.GeneratePackage(nupkg, packDiagnostics); } } diff --git a/src/dotnet/commands/dotnet-projectmodel-server/InternalModels/ProjectContextSnapshot.cs b/src/dotnet/commands/dotnet-projectmodel-server/InternalModels/ProjectContextSnapshot.cs index 01cb47e25..73c8f91d4 100644 --- a/src/dotnet/commands/dotnet-projectmodel-server/InternalModels/ProjectContextSnapshot.cs +++ b/src/dotnet/commands/dotnet-projectmodel-server/InternalModels/ProjectContextSnapshot.cs @@ -4,10 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.DotNet.Cli.Compiler.Common; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.ProjectModel.Graph; using Microsoft.DotNet.ProjectModel.Server.Helpers; using Microsoft.DotNet.ProjectModel.Server.Models; -using Microsoft.DotNet.Cli.Compiler.Common; using NuGet.Frameworks; namespace Microsoft.DotNet.ProjectModel.Server @@ -37,7 +38,7 @@ namespace Microsoft.DotNet.ProjectModel.Server .GetAllExports() .ToDictionary(export => export.Library.Identity.Name); - var allSourceFiles = new List(context.ProjectFile.Files.SourceFiles); + var allSourceFiles = new List(GetSourceFiles(context, configuration)); var allFileReferences = new List(); var allProjectReferences = new List(); var allDependencies = new Dictionary(); @@ -74,5 +75,19 @@ namespace Microsoft.DotNet.ProjectModel.Server return snapshot; } + + private static IEnumerable GetSourceFiles(ProjectContext context, string configuration) + { + var compilerOptions = context.ProjectFile.GetCompilerOptions(context.TargetFramework, configuration); + + if (compilerOptions.CompileInclude == null) + { + return context.ProjectFile.Files.SourceFiles; + } + + var includeFiles = IncludeFilesResolver.GetIncludeFiles(compilerOptions.CompileInclude, "/", diagnostics: null); + + return includeFiles.Select(f => f.SourcePath); + } } } diff --git a/src/dotnet/commands/dotnet-publish/PublishCommand.cs b/src/dotnet/commands/dotnet-publish/PublishCommand.cs index ee1012341..e89abb1bc 100644 --- a/src/dotnet/commands/dotnet-publish/PublishCommand.cs +++ b/src/dotnet/commands/dotnet-publish/PublishCommand.cs @@ -10,6 +10,7 @@ using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Files; using Microsoft.DotNet.ProjectModel; using Microsoft.DotNet.ProjectModel.Compilation; +using Microsoft.DotNet.ProjectModel.Files; using Microsoft.DotNet.ProjectModel.Graph; using Microsoft.DotNet.ProjectModel.Utilities; using Microsoft.DotNet.Tools.Common; @@ -179,7 +180,20 @@ namespace Microsoft.DotNet.Tools.Publish } var contentFiles = new ContentFiles(context); - contentFiles.StructuredCopyTo(outputPath); + + if (context.ProjectFile.PublishOptions != null) + { + var includeEntries = IncludeFilesResolver.GetIncludeFiles( + context.ProjectFile.PublishOptions, + PathUtility.EnsureTrailingSlash(outputPath), + diagnostics: null); + + contentFiles.StructuredCopyTo(outputPath, includeEntries); + } + else + { + contentFiles.StructuredCopyTo(outputPath); + } // Publish a host if this is an application if (options.EmitEntryPoint.GetValueOrDefault() && !string.IsNullOrEmpty(context.RuntimeIdentifier)) @@ -446,51 +460,6 @@ namespace Microsoft.DotNet.Tools.Publish return contexts.Select(c => Workspace.GetRuntimeContext(c, rids)); } - private static void CopyContents(ProjectContext context, string outputPath) - { - var contentFiles = context.ProjectFile.Files.GetContentFiles(); - Copy(contentFiles, context.ProjectDirectory, outputPath); - } - - private static void Copy(IEnumerable contentFiles, string sourceDirectory, string targetDirectory) - { - if (contentFiles == null) - { - throw new ArgumentNullException(nameof(contentFiles)); - } - - sourceDirectory = PathUtility.EnsureTrailingSlash(sourceDirectory); - targetDirectory = PathUtility.EnsureTrailingSlash(targetDirectory); - - foreach (var contentFilePath in contentFiles) - { - Reporter.Verbose.WriteLine($"Publishing {contentFilePath.Green().Bold()} ..."); - - var fileName = Path.GetFileName(contentFilePath); - - var targetFilePath = contentFilePath.Replace(sourceDirectory, targetDirectory); - var targetFileParentFolder = Path.GetDirectoryName(targetFilePath); - - // Create directory before copying a file - if (!Directory.Exists(targetFileParentFolder)) - { - Directory.CreateDirectory(targetFileParentFolder); - } - - File.Copy( - contentFilePath, - targetFilePath, - overwrite: true); - - // clear read-only bit if set - var fileAttributes = File.GetAttributes(targetFilePath); - if ((fileAttributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) - { - File.SetAttributes(targetFilePath, fileAttributes & ~FileAttributes.ReadOnly); - } - } - } - private static void RunScripts(ProjectContext context, string name, Dictionary contextVariables) { foreach (var script in context.ProjectFile.Scripts.GetOrEmpty(name)) diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateFileCollectionsFromJson.cs b/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateFileCollectionsFromJson.cs index 692d8fa4a..da368c0bb 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateFileCollectionsFromJson.cs +++ b/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateFileCollectionsFromJson.cs @@ -83,7 +83,6 @@ namespace Microsoft.DotNet.ProjectModel.Tests stream, ProjectName, ProjectFilePath, - new List(), settings); } } diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateIncludeEntriesFromJson.cs b/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateIncludeEntriesFromJson.cs new file mode 100644 index 000000000..58765ba23 --- /dev/null +++ b/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToCreateIncludeEntriesFromJson.cs @@ -0,0 +1,216 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using FluentAssertions; +using Microsoft.DotNet.ProjectModel.Files; +using Microsoft.DotNet.ProjectModel.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Xunit; + +namespace Microsoft.DotNet.ProjectModel.Tests +{ + public class GivenThatIWantToCreateIncludeEntriesFromJson + { + private const string ProjectName = "some project name"; + private readonly string ProjectFilePath = PathUtility.EnsureTrailingSlash(AppContext.BaseDirectory); + + [Fact] + public void PackInclude_is_null_when_it_is_not_set_in_the_ProjectJson() + { + var json = new JObject(); + var project = GetProject(json); + + project.PackOptions.PackInclude.Should().BeNull(); + } + + [Fact] + public void It_sets_PackInclude_when_packInclude_is_set_in_the_ProjectJson() + { + const string somePackTarget = "some pack target"; + const string somePackValue = "ff/files/file1.txt"; + + var json = JObject.Parse(string.Format(@"{{ +'packOptions': {{ + 'files': {{ + 'mappings': {{ + '{0}': {{ + 'includeFiles': '{1}' + }} + }} + }} +}}}}", somePackTarget, somePackValue)); + + CreateFile(somePackValue); + var project = GetProject(json); + + var packInclude = GetIncludeFiles(project.PackOptions.PackInclude, "/").FirstOrDefault(); + + packInclude.TargetPath.Should().Be(somePackTarget); + packInclude.SourcePath.Should().Contain(PathUtility.GetPathWithDirectorySeparator(somePackValue)); + } + + [Fact] + public void It_parses_compile_and_includes_files_successfully() + { + var json = JObject.Parse(@"{ +'buildOptions': { + 'compile': { + 'includeFiles': [ 'files/file1.cs', 'files/file2.cs' ], + 'exclude': 'files/*ex.cs' + } +}}"); + + CreateFile("files/file1.cs"); + CreateFile("files/file2.cs"); + CreateFile("files/file1ex.cs"); + CreateFile("files/file2ex.cs"); + + var project = GetProject(json); + + var compileInclude = GetIncludeFiles(project.GetCompilerOptions(null, null).CompileInclude, "/").ToArray(); + + compileInclude.Should().HaveCount(2); + + compileInclude.Should().Contain( + entry => entry.TargetPath == PathUtility.GetPathWithDirectorySeparator("files/file1.cs") && + entry.SourcePath.Contains(PathUtility.GetPathWithDirectorySeparator("files/file1.cs"))); + + compileInclude.Should().Contain( + entry => entry.TargetPath == PathUtility.GetPathWithDirectorySeparator("files/file2.cs") && + entry.SourcePath.Contains(PathUtility.GetPathWithDirectorySeparator("files/file2.cs"))); + } + + [Fact] + public void It_parses_namedResources_successfully() + { + const string someString = "some string"; + const string someResourcePattern = "files/*.resx"; + + var json = JObject.Parse(string.Format(@"{{ +'buildOptions': {{ + 'embed': {{ + 'mappings': {{ + '{0}': {{ + 'include': '{1}' + }} + }} + }} +}}}}", someString, someResourcePattern)); + + CreateFile("files/Resource.resx"); + + var project = GetProject(json); + + var embedInclude = GetIncludeFiles(project.GetCompilerOptions(null, null).EmbedInclude, "/").FirstOrDefault(); + + embedInclude.TargetPath.Should().Be(someString); + embedInclude.SourcePath.Should().Contain(PathUtility.GetPathWithDirectorySeparator("files/Resource.resx")); + } + + [Fact] + public void It_parses_copyToOutput_and_includes_files_successfully() + { + var json = JObject.Parse(@"{ +'buildOptions': { + 'copyToOutput': { + 'include': 'files/*.txt', + 'exclude': 'files/p*.txt', + 'excludeFiles': 'files/file1ex.txt', + } +}}"); + + CreateFile("files/file1.txt"); + CreateFile("files/file2.txt"); + CreateFile("files/file1ex.txt"); + + var project = GetProject(json); + + var copyToOutputInclude = GetIncludeFiles(project.GetCompilerOptions(null, null).CopyToOutputInclude, "/").ToArray(); + + copyToOutputInclude.Should().HaveCount(2); + + copyToOutputInclude.Should().Contain( + entry => entry.TargetPath == PathUtility.GetPathWithDirectorySeparator("files/file1.txt") && + entry.SourcePath.Contains(PathUtility.GetPathWithDirectorySeparator("files/file1.txt"))); + + copyToOutputInclude.Should().Contain( + entry => entry.TargetPath == PathUtility.GetPathWithDirectorySeparator("files/file2.txt") && + entry.SourcePath.Contains(PathUtility.GetPathWithDirectorySeparator("files/file2.txt"))); + } + + [Fact] + public void It_parses_PublishOptions_and_includes_files_successfully() + { + var json = JObject.Parse(@"{ +'publishOptions': { + 'include': 'files/p*.txt', + 'exclude': 'files/*ex.txt', + 'includeFiles': 'files/pfile2ex.txt' +}}"); + + CreateFile("files/pfile1.txt"); + CreateFile("files/pfile1ex.txt"); + CreateFile("files/pfile2ex.txt"); + + var project = GetProject(json); + + var publishOptions = GetIncludeFiles(project.PublishOptions, "/").ToArray(); + + publishOptions.Should().HaveCount(2); + + publishOptions.Should().Contain( + entry => entry.TargetPath == PathUtility.GetPathWithDirectorySeparator("files/pfile1.txt") && + entry.SourcePath.Contains(PathUtility.GetPathWithDirectorySeparator("files/pfile1.txt"))); + + publishOptions.Should().Contain( + entry => entry.TargetPath == PathUtility.GetPathWithDirectorySeparator("files/pfile2ex.txt") && + entry.SourcePath.Contains(PathUtility.GetPathWithDirectorySeparator("files/pfile2ex.txt"))); + } + + private Project GetProject(JObject json, ProjectReaderSettings settings = null) + { + using (var stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 256, true)) + { + using (var writer = new JsonTextWriter(sw)) + { + writer.Formatting = Formatting.Indented; + json.WriteTo(writer); + } + + stream.Position = 0; + var projectReader = new ProjectReader(); + return projectReader.ReadProject( + stream, + ProjectName, + ProjectFilePath, + settings); + } + } + } + + private IEnumerable GetIncludeFiles(IncludeContext context, string targetBasePath) + { + return IncludeFilesResolver.GetIncludeFiles(context, targetBasePath, null); + } + + private void CreateFile(string filePath) + { + filePath = Path.Combine(ProjectFilePath, filePath); + var dirName = Path.GetDirectoryName(filePath); + if (!Directory.Exists(dirName)) + { + Directory.CreateDirectory(dirName); + } + + File.Create(filePath); + } + } +} diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToLoadAProjectJsonFile.cs b/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToLoadAProjectJsonFile.cs index 4d57970fb..b58f77dbd 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToLoadAProjectJsonFile.cs +++ b/test/Microsoft.DotNet.ProjectModel.Tests/GivenThatIWantToLoadAProjectJsonFile.cs @@ -22,6 +22,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests private const string ProjectName = "some project name"; private const string SomeLanguageVersion = "some language version"; private const string SomeOutputName = "some output name"; + private const string SomeCompilerName = "some compiler name"; private const string SomePlatform = "some platform"; private const string SomeKeyFile = "some key file"; private const string SomeDebugType = "some debug type"; @@ -49,6 +50,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests _jsonCompilationOptions.Add("additionalArguments", new JArray(_someAdditionalArguments)); _jsonCompilationOptions.Add("languageVersion", SomeLanguageVersion); _jsonCompilationOptions.Add("outputName", SomeOutputName); + _jsonCompilationOptions.Add("compilerName", SomeCompilerName); _jsonCompilationOptions.Add("platform", SomePlatform); _jsonCompilationOptions.Add("keyFile", SomeKeyFile); _jsonCompilationOptions.Add("debugType", SomeDebugType); @@ -68,6 +70,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests AdditionalArguments = _someAdditionalArguments, LanguageVersion = SomeLanguageVersion, OutputName = SomeOutputName, + CompilerName = SomeCompilerName, Platform = SomePlatform, KeyFile = SomeKeyFile, DebugType = SomeDebugType, @@ -168,18 +171,18 @@ namespace Microsoft.DotNet.ProjectModel.Tests public void It_leaves_marketing_information_empty_when_it_is_not_set_in_the_ProjectJson() { _emptyProject.Description.Should().BeNull(); - _emptyProject.Summary.Should().BeNull(); + _emptyProject.PackOptions.Summary.Should().BeNull(); _emptyProject.Copyright.Should().BeNull(); _emptyProject.Title.Should().BeNull(); _emptyProject.EntryPoint.Should().BeNull(); - _emptyProject.ProjectUrl.Should().BeNull(); - _emptyProject.LicenseUrl.Should().BeNull(); - _emptyProject.IconUrl.Should().BeNull(); + _emptyProject.PackOptions.ProjectUrl.Should().BeNull(); + _emptyProject.PackOptions.LicenseUrl.Should().BeNull(); + _emptyProject.PackOptions.IconUrl.Should().BeNull(); _emptyProject.Authors.Should().BeEmpty(); - _emptyProject.Owners.Should().BeEmpty(); - _emptyProject.Tags.Should().BeEmpty(); + _emptyProject.PackOptions.Owners.Should().BeEmpty(); + _emptyProject.PackOptions.Tags.Should().BeEmpty(); _emptyProject.Language.Should().BeNull(); - _emptyProject.ReleaseNotes.Should().BeNull(); + _emptyProject.PackOptions.ReleaseNotes.Should().BeNull(); } [Fact] @@ -216,24 +219,105 @@ namespace Microsoft.DotNet.ProjectModel.Tests var project = GetProject(json); project.Description.Should().Be(someDescription); - project.Summary.Should().Be(someSummary); + project.PackOptions.Summary.Should().Be(someSummary); project.Copyright.Should().Be(someCopyright); project.Title.Should().Be(someTitle); project.EntryPoint.Should().Be(someEntryPoint); - project.ProjectUrl.Should().Be(someProjectUrl); - project.LicenseUrl.Should().Be(someLicenseUrl); - project.IconUrl.Should().Be(someIconUrl); + project.PackOptions.ProjectUrl.Should().Be(someProjectUrl); + project.PackOptions.LicenseUrl.Should().Be(someLicenseUrl); + project.PackOptions.IconUrl.Should().Be(someIconUrl); project.Authors.Should().Contain(authors); - project.Owners.Should().Contain(owners); - project.Tags.Should().Contain(tags); + project.PackOptions.Owners.Should().Contain(owners); + project.PackOptions.Tags.Should().Contain(tags); project.Language.Should().Be(someLanguage); - project.ReleaseNotes.Should().Be(someReleaseNotes); + project.PackOptions.ReleaseNotes.Should().Be(someReleaseNotes); + } + + [Fact] + public void It_sets_the_marketing_information_when_it_is_set_in_the_ProjectJson_PackOptions() + { + const string someDescription = "some description"; + const string someSummary = "some summary"; + const string someCopyright = "some copyright"; + const string someTitle = "some title"; + const string someEntryPoint = "some entry point"; + const string someProjectUrl = "some project url"; + const string someLicenseUrl = "some license url"; + const string someIconUrl = "some icon url"; + const string someLanguage = "some language"; + const string someReleaseNotes = "someReleaseNotes"; + var authors = new[] { "some author", "and another author" }; + var owners = new[] { "some owner", "a second owner" }; + var tags = new[] { "tag1", "tag2" }; + + var json = new JObject(); + var packOptions = new JObject(); + + json.Add("description", someDescription); + json.Add("copyright", someCopyright); + json.Add("title", someTitle); + json.Add("entryPoint", someEntryPoint); + json.Add("authors", new JArray(authors)); + json.Add("language", someLanguage); + packOptions.Add("summary", someSummary); + packOptions.Add("projectUrl", someProjectUrl); + packOptions.Add("licenseUrl", someLicenseUrl); + packOptions.Add("iconUrl", someIconUrl); + packOptions.Add("owners", new JArray(owners)); + packOptions.Add("tags", new JArray(tags)); + packOptions.Add("releaseNotes", someReleaseNotes); + json.Add("packOptions", packOptions); + + var project = GetProject(json); + + project.Description.Should().Be(someDescription); + project.PackOptions.Summary.Should().Be(someSummary); + project.Copyright.Should().Be(someCopyright); + project.Title.Should().Be(someTitle); + project.EntryPoint.Should().Be(someEntryPoint); + project.PackOptions.ProjectUrl.Should().Be(someProjectUrl); + project.PackOptions.LicenseUrl.Should().Be(someLicenseUrl); + project.PackOptions.IconUrl.Should().Be(someIconUrl); + project.Authors.Should().Contain(authors); + project.PackOptions.Owners.Should().Contain(owners); + project.PackOptions.Tags.Should().Contain(tags); + project.Language.Should().Be(someLanguage); + project.PackOptions.ReleaseNotes.Should().Be(someReleaseNotes); + } + + [Fact] + public void It_warns_when_deprecated_schema_is_used() + { + var json = new JObject(); + + json.Add("compilerName", "some compiler"); + json.Add("compilationOptions", new JObject()); + json.Add("projectUrl", "some project url"); + + var project = GetProject(json); + + project.Diagnostics.Should().HaveCount(3); + + project.Diagnostics.Should().Contain(m => + m.ErrorCode == ErrorCodes.DOTNET1015 && + m.Severity == DiagnosticMessageSeverity.Warning && + m.Message == "The 'compilationOptions' option is deprecated. Use 'buildOptions' instead."); + + project.Diagnostics.Should().Contain(m => + m.ErrorCode == ErrorCodes.DOTNET1016 && + m.Severity == DiagnosticMessageSeverity.Warning && + m.Message == "The 'projectUrl' option in the root is deprecated. Use it in 'packOptions' instead."); + + project.Diagnostics.Should().Contain(m => + m.ErrorCode == ErrorCodes.DOTNET1016 && + m.Severity == DiagnosticMessageSeverity.Warning && + m.Message == "The 'compilerName' option in the root is deprecated. Use it in 'buildOptions' instead."); } [Fact] public void It_sets_the_compilerName_to_csc_when_one_is_not_set_in_the_ProjectJson() { - _emptyProject.CompilerName.Should().Be("csc"); + _emptyProject.GetCompilerOptions(targetFramework: null, configurationName: null).CompilerName.Should().Be("csc"); } [Fact] @@ -244,7 +328,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests json.Add("compilerName", compilerName); var project = GetProject(json); - project.CompilerName.Should().Be(compilerName); + project.GetCompilerOptions(targetFramework: null, configurationName: null).CompilerName.Should().Be(compilerName); } [Fact] @@ -267,7 +351,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests [Fact] public void It_sets_requireLicenseAcceptance_to_false_when_one_is_not_set_in_the_ProjectJson() { - _emptyProject.RequireLicenseAcceptance.Should().BeFalse(); + _emptyProject.PackOptions.RequireLicenseAcceptance.Should().BeFalse(); } [Fact] @@ -277,7 +361,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests json.Add("requireLicenseAcceptance", true); var project = GetProject(json); - project.RequireLicenseAcceptance.Should().BeTrue(); + project.PackOptions.RequireLicenseAcceptance.Should().BeTrue(); } [Fact] @@ -287,7 +371,7 @@ namespace Microsoft.DotNet.ProjectModel.Tests json.Add("requireLicenseAcceptance", false); var project = GetProject(json); - project.RequireLicenseAcceptance.Should().BeFalse(); + project.PackOptions.RequireLicenseAcceptance.Should().BeFalse(); } [Fact] @@ -405,7 +489,8 @@ namespace Microsoft.DotNet.ProjectModel.Tests { _emptyProject.GetCompilerOptions(null, null).Should().Be(new CommonCompilerOptions { - OutputName = ProjectName + OutputName = ProjectName, + CompilerName = "csc" }); } @@ -471,6 +556,17 @@ namespace Microsoft.DotNet.ProjectModel.Tests project.GetCompilerOptions(null, null).Should().Be(_commonCompilerOptions); } + [Fact] + public void It_sets_buildOptions_when_it_is_set_in_the_compilationOptions_in_the_ProjectJson() + { + var json = new JObject(); + json.Add("buildOptions", _jsonCompilationOptions); + + var project = GetProject(json); + + project.GetCompilerOptions(null, null).Should().Be(_commonCompilerOptions); + } + [Fact] public void It_merges_configuration_sections_set_in_the_ProjectJson() { @@ -874,7 +970,6 @@ namespace Microsoft.DotNet.ProjectModel.Tests stream, ProjectName, ProjectFilePath, - new List(), settings); } } diff --git a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs index ea5571cc2..df7a0db8b 100644 --- a/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs +++ b/test/Microsoft.DotNet.ProjectModel.Tests/LibraryExporterPackageTests.cs @@ -336,7 +336,10 @@ namespace Microsoft.DotNet.ProjectModel.Tests var rootProject = new Project() { Name = "RootProject", - CompilerName = "csc" + _defaultCompilerOptions = new CommonCompilerOptions + { + CompilerName = "csc" + } }; var rootProjectDescription = new ProjectDescription( diff --git a/test/dotnet-compile.Tests/CompilerTests.cs b/test/dotnet-compile.Tests/CompilerTests.cs index 008dc853c..d6f1792fb 100644 --- a/test/dotnet-compile.Tests/CompilerTests.cs +++ b/test/dotnet-compile.Tests/CompilerTests.cs @@ -147,6 +147,48 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests result.StdOut.Should().Contain("MyNamespace.Util"); } + [Fact] + public void EmbeddedResourcesAreCopied() + { + var testInstance = TestAssetsManager.CreateTestInstance("EndToEndTestApp") + .WithLockFiles() + .WithBuildArtifacts(); + + var root = testInstance.TestRoot; + + // run compile + var outputDir = Path.Combine(root, "bin"); + var testProject = ProjectUtils.GetProjectJson(root, "EndToEndTestApp"); + var buildCommand = new BuildCommand(testProject, output: outputDir, framework: DefaultFramework); + var result = buildCommand.ExecuteWithCapturedOutput(); + result.Should().Pass(); + + var objDirInfo = new DirectoryInfo(Path.Combine(root, "obj", "Debug", DefaultFramework)); + objDirInfo.Should().HaveFile("EndToEndTestApp.resource1.resources"); + objDirInfo.Should().HaveFile("myresource.resources"); + } + + [Fact] + public void CopyToOutputFilesAreCopied() + { + var testInstance = TestAssetsManager.CreateTestInstance("EndToEndTestApp") + .WithLockFiles() + .WithBuildArtifacts(); + + var root = testInstance.TestRoot; + + // run compile + var outputDir = Path.Combine(root, "bin"); + var testProject = ProjectUtils.GetProjectJson(root, "EndToEndTestApp"); + var buildCommand = new BuildCommand(testProject, output: outputDir, framework: DefaultFramework); + var result = buildCommand.ExecuteWithCapturedOutput(); + result.Should().Pass(); + + var outputDirInfo = new DirectoryInfo(Path.Combine(outputDir, "copy")); + outputDirInfo.Should().HaveFile("file.txt"); + outputDirInfo.Should().NotHaveFile("fileex.txt"); + } + [Fact] public void CanSetOutputAssemblyNameForLibraries() { diff --git a/test/dotnet-pack.Tests/PackTests.cs b/test/dotnet-pack.Tests/PackTests.cs index aa9ccca26..75778fab6 100644 --- a/test/dotnet-pack.Tests/PackTests.cs +++ b/test/dotnet-pack.Tests/PackTests.cs @@ -95,6 +95,24 @@ namespace Microsoft.DotNet.Tools.Compiler.Tests zip.Entries.Should().Contain(e => e.FullName == "lib/netstandard1.5/TestLibraryWithConfiguration.dll"); } + [Fact] + public void HasIncludedFiles() + { + var testInstance = TestAssetsManager.CreateTestInstance("EndToEndTestApp") + .WithLockFiles() + .WithBuildArtifacts(); + + var cmd = new PackCommand(Path.Combine(testInstance.TestRoot, Project.FileName)); + cmd.Execute().Should().Pass(); + + var outputPackage = Path.Combine(testInstance.TestRoot, "bin", "Debug", "EndToEndTestApp.1.0.0.nupkg"); + File.Exists(outputPackage).Should().BeTrue(outputPackage); + + var zip = ZipFile.Open(outputPackage, ZipArchiveMode.Read); + zip.Entries.Should().Contain(e => e.FullName == "pack1.txt"); + zip.Entries.Should().Contain(e => e.FullName == "newpath/pack2.txt"); + } + [Fact] public void PackAddsCorrectFilesForProjectsWithOutputNameSpecified() { diff --git a/test/dotnet-publish.Tests/PublishTests.cs b/test/dotnet-publish.Tests/PublishTests.cs index 809e41234..7a186511e 100644 --- a/test/dotnet-publish.Tests/PublishTests.cs +++ b/test/dotnet-publish.Tests/PublishTests.cs @@ -116,6 +116,21 @@ namespace Microsoft.DotNet.Tools.Publish.Tests publishCommand.GetOutputDirectory().Should().HaveFile("testcontentfile.txt"); } + [Fact] + public void ProjectWithPublishOptionsTest() + { + var instance = TestAssetsManager.CreateTestInstance("EndToEndTestApp") + .WithLockFiles() + .WithBuildArtifacts(); + + var testProject = _getProjectJson(instance.TestRoot, "EndToEndTestApp"); + + var publishCommand = new PublishCommand(testProject); + + publishCommand.Execute().Should().Pass(); + publishCommand.GetOutputDirectory().Should().HaveFile("testpublishfile.txt"); + } + [Fact] public void FailWhenNoRestoreTest() {