Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide option for msbuild properties #1549

Merged
merged 2 commits into from
Apr 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions RELEASENOTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,70 @@ v2.16(Pre-Release)
1. Mustache: `{{!master('<master_page_name>')}}`
2. Liquid: `{% master <master_page_name> %}`

2. Support the latest csproj format `<Project Sdk="Microsoft.NET.Sdk">`
1. The latest csproj introduces in a new property `TargetFrameworks`, docfx does not support it for now. To make docfx work, please specify `TargetFramework` when calling docfx. A sample `docfx.json` would be as follows. The `merge` command is to merge YAML files generated with different `TargetFramework` into one YAML file.
```json
{
"metadata": [
{
"src": "*.csproj",
"dest": "temp/api/netstandard1.4",
"properties": {
"TargetFramework": "netstandard1.4"
}
},
{
"src": "*.csproj",
"dest": "temp/api/net46",
"properties": {
"TargetFramework": "net46"
}
}
],
"merge": {
"content": [
{
"files": "*.yml",
"src": "temp/api/netstandard1.4"
},
{
"files": "*.yml",
"src": "temp/api/net46"
}
],
"fileMetadata": {
"platform": {
"temp/api/netstandard1.4/*.yml": [
"netstandard1.4"
],
"temp/api/net46/*.yml": [
"net46"
]
}
},
"dest": "api"
},
"build": {
"content": [
{
"files": [
"api/*.yml",
"**.md",
"**/toc.yml"
]
}
],
"dest": "_site"
}
}
```

v2.15
-----------
1. Bug fixes:
1. Auto dedent the included code snippet, both when including the whole file and file sections.
2. [Breaking Change]For inline inclusion, trim ending white spaces, considering ending white spaces in inline inclusion in most cases are typos.
2. Support the latest csproj format `<Project Sdk="Microsoft.NET.Sdk">`
3. Following GitHub markdown behavior changes.
2. Following GitHub markdown behavior changes.

v2.14
-----------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public class ExtractMetadataInputModel

public string GlobalNamespaceId { get; set; }

public Dictionary<string, string> MSBuildProperties { get; set; }

public override string ToString()
{
using(StringWriter writer = new StringWriter())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ namespace Microsoft.DocAsCode.Metadata.ManagedReference

public sealed class ExtractMetadataWorker : IDisposable
{
private readonly Lazy<MSBuildWorkspace> _workspace = new Lazy<MSBuildWorkspace>(() => MSBuildWorkspace.Create(new Dictionary<string, string>
{
{ "Configuration", "Release" }
}));
private static readonly string[] SupportedSolutionExtensions = { ".sln" };
private static readonly string[] SupportedProjectName = { "project.json" };
private static readonly string[] SupportedProjectExtensions = { ".csproj", ".vbproj" };
Expand All @@ -44,6 +40,9 @@ public sealed class ExtractMetadataWorker : IDisposable
private readonly string _filterConfigFile;
private readonly bool _useCompatibilityFileName;

// TODO: refactor incremental logic
private readonly Dictionary<string, string> _msbuildProperties;
private readonly Lazy<MSBuildWorkspace> _workspace;
static ExtractMetadataWorker()
{
SupportedExtensions.AddRange(SupportedSolutionExtensions);
Expand All @@ -64,6 +63,14 @@ public ExtractMetadataWorker(ExtractMetadataInputModel input, bool rebuild, bool
_filterConfigFile = Path.GetFullPath(Path.Combine(EnvironmentContext.BaseDirectory, input.FilterConfigFile)).Normalize();
}
_useCompatibilityFileName = useCompatibilityFileName;

_msbuildProperties = input.MSBuildProperties ?? new Dictionary<string, string>();
if (!_msbuildProperties.ContainsKey("Configuration"))
{
_msbuildProperties["Configuration"] = "Release";
}

_workspace = new Lazy<MSBuildWorkspace>(() => MSBuildWorkspace.Create(_msbuildProperties));
}

public async Task ExtractMetadataAsync()
Expand Down Expand Up @@ -361,7 +368,7 @@ await projects.ForEachInParallelAsync(async path =>
{
IncrementalCheck check = new IncrementalCheck(buildInfo);
// 1. Check if sln files/ project files and its contained documents/ source files are modified
var projectModified = check.AreFilesModified(documentCache.Documents);
var projectModified = check.AreFilesModified(documentCache.Documents) || check.MSBuildPropertiesUpdated(_msbuildProperties);

if (!projectModified)
{
Expand Down Expand Up @@ -476,14 +483,14 @@ where File.Exists(xmlFile)
{
var value = StringExtension.ToDelimitedString(projectMetadataList.Select(s => s.Name));
Logger.Log(LogLevel.Warning, $"No metadata is generated for {value}.");
applicationCache.SaveToCache(inputs, null, triggeredTime, outputFolder, null, _shouldSkipMarkup);
applicationCache.SaveToCache(inputs, null, triggeredTime, outputFolder, null, _shouldSkipMarkup, _msbuildProperties);
}
else
{
// TODO: need an intermediate folder? when to clean it up?
// Save output to output folder
var outputFiles = ResolveAndExportYamlMetadata(allMemebers, allReferences, outputFolder, _validInput.IndexFileName, _validInput.TocFileName, _validInput.ApiFolderName, _preserveRawInlineComments, _shouldSkipMarkup, _rawInput.ExternalReferences, _useCompatibilityFileName);
applicationCache.SaveToCache(inputs, documentCache.Cache, triggeredTime, outputFolder, outputFiles, _shouldSkipMarkup);
applicationCache.SaveToCache(inputs, documentCache.Cache, triggeredTime, outputFolder, outputFiles, _shouldSkipMarkup, _msbuildProperties);
}
}

Expand Down Expand Up @@ -581,14 +588,14 @@ private static void CopyFromCachedResult(BuildInfo buildInfo, IEnumerable<string
PathUtility.CopyFilesToFolder(relativeFiles.Select(s => Path.Combine(outputFolderSource, s)), outputFolderSource, outputFolder, true, s => Logger.Log(LogLevel.Info, s), null);
}

private static Task<Tuple<MetadataItem, bool>> GetProjectMetadataFromCacheAsync(Project project, Compilation compilation, string outputFolder, ProjectDocumentCache documentCache, bool forceRebuild, bool shouldSkipMarkup, bool preserveRawInlineComments, string filterConfigFile, IReadOnlyDictionary<Compilation, IEnumerable<IMethodSymbol>> extensionMethods, bool isReferencedProjectRebuilt)
private Task<Tuple<MetadataItem, bool>> GetProjectMetadataFromCacheAsync(Project project, Compilation compilation, string outputFolder, ProjectDocumentCache documentCache, bool forceRebuild, bool shouldSkipMarkup, bool preserveRawInlineComments, string filterConfigFile, IReadOnlyDictionary<Compilation, IEnumerable<IMethodSymbol>> extensionMethods, bool isReferencedProjectRebuilt)
{
var projectFilePath = project.FilePath;
var k = documentCache.GetDocuments(projectFilePath);
return GetMetadataFromProjectLevelCacheAsync(
project,
new[] { projectFilePath, filterConfigFile },
s => Task.FromResult(forceRebuild || s.AreFilesModified(k.Concat(new string[] { filterConfigFile })) || isReferencedProjectRebuilt),
s => Task.FromResult(forceRebuild || s.AreFilesModified(k.Concat(new string[] { filterConfigFile })) || isReferencedProjectRebuilt || s.MSBuildPropertiesUpdated(_msbuildProperties)),
s => Task.FromResult(compilation),
s => Task.FromResult(compilation.Assembly),
s =>
Expand All @@ -602,7 +609,7 @@ private static Task<Tuple<MetadataItem, bool>> GetProjectMetadataFromCacheAsync(
extensionMethods);
}

private static Task<Tuple<MetadataItem, bool>> GetAssemblyMetadataFromCacheAsync(IEnumerable<string> files, Compilation compilation, IAssemblySymbol assembly, string outputFolder, bool forceRebuild, string filterConfigFile, IReadOnlyDictionary<Compilation, IEnumerable<IMethodSymbol>> extensionMethods)
private Task<Tuple<MetadataItem, bool>> GetAssemblyMetadataFromCacheAsync(IEnumerable<string> files, Compilation compilation, IAssemblySymbol assembly, string outputFolder, bool forceRebuild, string filterConfigFile, IReadOnlyDictionary<Compilation, IEnumerable<IMethodSymbol>> extensionMethods)
{
if (files == null || !files.Any()) return null;
return GetMetadataFromProjectLevelCacheAsync(
Expand All @@ -618,12 +625,12 @@ private static Task<Tuple<MetadataItem, bool>> GetAssemblyMetadataFromCacheAsync
extensionMethods);
}

private static Task<Tuple<MetadataItem, bool>> GetFileMetadataFromCacheAsync(IEnumerable<string> files, Compilation compilation, string outputFolder, bool forceRebuild, bool shouldSkipMarkup, bool preserveRawInlineComments, string filterConfigFile, IReadOnlyDictionary<Compilation, IEnumerable<IMethodSymbol>> extensionMethods)
private Task<Tuple<MetadataItem, bool>> GetFileMetadataFromCacheAsync(IEnumerable<string> files, Compilation compilation, string outputFolder, bool forceRebuild, bool shouldSkipMarkup, bool preserveRawInlineComments, string filterConfigFile, IReadOnlyDictionary<Compilation, IEnumerable<IMethodSymbol>> extensionMethods)
{
if (files == null || !files.Any()) return null;
return GetMetadataFromProjectLevelCacheAsync(
files,
files.Concat(new string[] { filterConfigFile }), s => Task.FromResult(forceRebuild || s.AreFilesModified(files.Concat(new string[] { filterConfigFile }))),
files.Concat(new string[] { filterConfigFile }), s => Task.FromResult(forceRebuild || s.AreFilesModified(files.Concat(new string[] { filterConfigFile })) || s.MSBuildPropertiesUpdated(_msbuildProperties)),
s => Task.FromResult(compilation),
s => Task.FromResult(compilation.Assembly),
s => null,
Expand All @@ -634,7 +641,7 @@ private static Task<Tuple<MetadataItem, bool>> GetFileMetadataFromCacheAsync(IEn
extensionMethods);
}

private static async Task<Tuple<MetadataItem, bool>> GetMetadataFromProjectLevelCacheAsync<T>(
private async Task<Tuple<MetadataItem, bool>> GetMetadataFromProjectLevelCacheAsync<T>(
T input,
IEnumerable<string> inputKey,
Func<IncrementalCheck, Task<bool>> rebuildChecker,
Expand Down Expand Up @@ -691,7 +698,7 @@ private static async Task<Tuple<MetadataItem, bool>> GetMetadataFromProjectLevel
}

// Save to cache
projectLevelCache.SaveToCache(inputKey, containedFiles, triggeredTime, cacheOutputFolder, new List<string>() { file }, shouldSkipMarkup);
projectLevelCache.SaveToCache(inputKey, containedFiles, triggeredTime, cacheOutputFolder, new List<string>() { file }, shouldSkipMarkup, _msbuildProperties);

return Tuple.Create(projectMetadata, rebuildProject);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ internal class BuildInfo
/// </summary>
public IDictionary<string, List<string>> ContainedFiles { get; set; }

public IDictionary<string, string> MSBuildProperties { get; set; }

public string CheckSum { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public BuildInfo GetValidConfig(IEnumerable<string> inputProjects)
return GetConfig(key);
}

public void SaveToCache(IEnumerable<string> inputProjects, IDictionary<string, List<string>> containedFiles, DateTime triggeredTime, string outputFolder, IList<string> fileRelativePaths, bool shouldSkipMarkup)
public void SaveToCache(IEnumerable<string> inputProjects, IDictionary<string, List<string>> containedFiles, DateTime triggeredTime, string outputFolder, IList<string> fileRelativePaths, bool shouldSkipMarkup, IDictionary<string, string> msbuildProperties)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create a class for parameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do refactor later

{
var key = StringExtension.GetNormalizedFullPathKey(inputProjects);
DateTime completeTime = DateTime.UtcNow;
Expand All @@ -51,7 +51,8 @@ public void SaveToCache(IEnumerable<string> inputProjects, IDictionary<string, L
OutputFolder = StringExtension.ToNormalizedFullPath(outputFolder),
RelatvieOutputFiles = StringExtension.GetNormalizedPathList(fileRelativePaths),
BuildAssembly = AssemblyName,
ShouldSkipMarkup = shouldSkipMarkup
ShouldSkipMarkup = shouldSkipMarkup,
MSBuildProperties = msbuildProperties,
};
this.SaveConfig(key, info);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Microsoft.DocAsCode.Metadata.ManagedReference
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using System.Linq;

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
Expand All @@ -23,6 +23,7 @@ internal class IncrementalCheck
private ConcurrentDictionary<string, VersionStamp> _metadataVersionCache;
private AsyncConcurrentCache<string, bool> _projectUpToDateSnapshot;
private bool _versionChanged;
private readonly BuildInfo _buildInfo;
public IncrementalCheck(BuildInfo buildInfo)
{
var checkUtcTime = buildInfo.TriggeredUtcTime;
Expand All @@ -36,7 +37,7 @@ public IncrementalCheck(BuildInfo buildInfo)
Logger.Log(LogLevel.Verbose, $"Assembly '{version ?? "<undefined>"}' when last build took place is not current assembly '{currentVersion}', rebuild required");
}
}

_buildInfo = buildInfo;
_versionToBeCompared = VersionStamp.Create(checkUtcTime);
_metadataVersionCache = new ConcurrentDictionary<string, VersionStamp>();
_projectUpToDateSnapshot = new AsyncConcurrentCache<string, bool>();
Expand All @@ -53,6 +54,11 @@ public bool AreFilesModified(IEnumerable<string> files)
return false;
}

public bool MSBuildPropertiesUpdated(IDictionary<string, string> newProperties)
{
return !DictionaryEqual(_buildInfo.MSBuildProperties, newProperties);
}

/// <summary>
/// If file does not exists, return **true**?? ==> should have checked exists before calling.
/// If file's last modified time is newer, return true; otherwise, return false
Expand Down Expand Up @@ -95,91 +101,33 @@ private static bool VersionNewer(VersionStamp thisVersion, VersionStamp thatVers
return false;
}

/// <summary>
/// Load all the version this project source code dependent on:
/// 1. project file version;
/// 2. document version;
/// 3. assembly reference version
/// TODO: In which case do project references not in current solution?
/// And save to global storage
/// </summary>
/// <param name="project"></param>
public async Task<bool> IsSingleProjectChanged(Project project)
private static VersionStamp GetLastModifiedVersionForFile(string filePath)
{
if (_versionChanged) return true;
// 1. project file itself changed since <date>
var version = GetLastModifiedVersionForFile(project.FilePath);
var dateTime = File.GetLastWriteTimeUtc(filePath);
return VersionStamp.Create(dateTime);
}

if (VersionNewer(version))
private static bool DictionaryEqual<TKey, TValue>(IDictionary<TKey, TValue> dict1, IDictionary<TKey, TValue> dict2, IEqualityComparer<TValue> equalityComparer = null)
{
if (Equals(dict1, dict2))
{
Logger.Log(LogLevel.Verbose, $"project file '{project.Name}' version '{version.ToString()}' newer than '{_versionToBeCompared.ToString()}'");
return true;
}
else
{
Logger.Log(LogLevel.Verbose, $"project file '{project.Name}' version '{version.ToString()}' older than '{_versionToBeCompared.ToString()}', no need to rebuild");
}

// 2. project's containing source files changed since <date>
var documents = project.Documents;
foreach (var document in documents)
if (dict1 == null || dict2 == null || dict1.Count != dict2.Count)
{
// Incase new document added into project however project file is not changed
// e.g. in kproj or csproj by <Compile Include="*.cs"/>
VersionStamp documentVersion = await document.GetTextVersionAsync();
var path = document.FilePath;
if (!string.IsNullOrEmpty(path))
{
var createdTime = GetCreatedVersionForFile(path);
documentVersion = VersionNewer(documentVersion, createdTime) ? documentVersion : createdTime;
}

if (VersionNewer(documentVersion))
{
Logger.Log(LogLevel.Verbose, $"document '{document.Name}' version '{documentVersion.ToString()}' newer than '{_versionToBeCompared.ToString()}'");
return true;
}
else
{
Logger.Log(LogLevel.Verbose, $"document '{document.Name}' version '{documentVersion.ToString()}' older than '{_versionToBeCompared.ToString()}', no need to rebuild");
}
return false;
}

// 3. project's assembly reference changed since <date>
var assemblyReferences = project.MetadataReferences;
foreach (var assemblyReference in assemblyReferences)
if (equalityComparer == null)
{
var executableReference = assemblyReference as PortableExecutableReference;
if (executableReference != null)
{
var filePath = executableReference.FilePath;
var assemblyVersion = _metadataVersionCache.GetOrAdd(filePath, s => GetLastModifiedVersionForFile(s));
if (VersionNewer(assemblyVersion))
{
Console.WriteLine($"document {filePath} version {assemblyVersion} newer than {_versionToBeCompared}");
return true;
}
}
equalityComparer = EqualityComparer<TValue>.Default;
}

// TODO: In which case do project references not in current solution?
// EXAMPLE: <Roslyn>/VBCSCompilerTests, contains 11 project references, however 9 of them are in solution
// vbc2.vcxproj and vbc2.vcxproj are not in solution. Currently consider it as irrelavate to source code rebuild
var projectReferences = project.AllProjectReferences;

return false;
}

private static VersionStamp GetLastModifiedVersionForFile(string filePath)
{
var dateTime = File.GetLastWriteTimeUtc(filePath);
return VersionStamp.Create(dateTime);
}

private static VersionStamp GetCreatedVersionForFile(string filePath)
{
var dateTime = File.GetCreationTimeUtc(filePath);
return VersionStamp.Create(dateTime);
return dict1.All(pair =>
{
return dict2.TryGetValue(pair.Key, out TValue val) && equalityComparer.Equals(pair.Value, val);
});
}
}
}
Loading