Skip to content
This repository was archived by the owner on Jul 14, 2020. It is now read-only.

Add aggregatePackage module#80

Merged
kingerja merged 10 commits intoCommonBuildToolset:masterfrom
kingerja:iss60
May 3, 2017
Merged

Add aggregatePackage module#80
kingerja merged 10 commits intoCommonBuildToolset:masterfrom
kingerja:iss60

Conversation

@kingerja
Copy link
Copy Markdown
Contributor

Create a basic aggregate package option where the user can set a property like.
<CBTAggregatePackage>AAG_TEST=d:\tmp\aag\src*d:\tmp\aag\src2|d:\tmp\aag\rem1</CBTAggregatePackage>

And it will use copy d:\tmp\agg\src then overlay d:\tmp\aag\src2 then remove from that overlay any files with names that match in d:\tmp\aag\rem1.

Pay attention to the comments there are many issues we need to consider with this approach.

To address Issue #60

@kingerja kingerja requested a review from josemesona April 27, 2017 01:53
Copy link
Copy Markdown
Contributor

@jeffkl jeffkl left a comment

Choose a reason for hiding this comment

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

Overall this looks great

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Configuration">
<Description>Provides Global Package expansion for CBT.Nuget.</Description>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Update this description.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed


<PropertyGroup>
<RestoreNuGetPackagesDependsOn>$(RestoreNuGetPackagesDependsOn);AggregateNugetPackages</RestoreNuGetPackagesDependsOn>
<RestoreDependsOn>$(RestoreDependsOn);AggregateNugetPackages</RestoreDependsOn>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You shouldn't need this if you're setting RestoreNuGetPackagesDependsOn. If it needs to be set, I'd say that's a bug.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This appears to be an issue in the globalpackages as well.
....\src\CBT.NuGet.GlobalRestore\CBT\Module\After.Microsoft.Common.targets: RestoreGlobalNuGetPackages;$(RestoreDependsOn)

I have filed an issue.


<PropertyGroup Condition=" '$(CBTEnableAggregatePackageRestore)' == 'true' ">
<CBTNuGetAggregatePackagePropertyFile Condition=" '$(CBTNuGetAggregatePackagePropertyFile)' == '' ">$(IntermediateOutputPath)\AggregatePackages.props</CBTNuGetAggregatePackagePropertyFile>
<CBTNuGetAggregatePackagePropertiesCreated>$(CBTNuGetTasksAssemblyPath.GetType().Assembly.GetType('System.AppDomain').GetProperty('CurrentDomain').GetValue(null).CreateInstanceFromAndUnwrap($(CBTNuGetTasksAssemblyPath), 'CBT.NuGet.Tasks.AggregatePackages').Execute('$(CBTAggregateDestPackageRoot)', '$(CBTAggregatePackage)', '$(CBTNuGetAggregatePackagePropertyFile)'))</CBTNuGetAggregatePackagePropertiesCreated>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We'll need to figure out how to make this work in Visual Studio...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It would be neat if we could come up with a generic pattern or solution for these hacks something where it would do a popup in vs saying doing custom cbt action'somecustomerrormessageforaction'.

Remove,
}

public AggregatePackage(string outPropertyId, List<KeyValuePair<AggregateOption, string>> packagesToAggregate, string destinationRoot)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is packagesToAggregate a List<T> because it can contain duplicates or can it be an IDictionary<TKey, TValue>?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I was just creating an ordered list of tuple pairs with <Action, Folder> action being add or remove from aggregate and folder being the folder that should be added to the aggregate or have it's contents removed from the aggregate.

Instead of using Tuple I just used a KVP which technically isn't correct. If I were to make the folder path a key then that could be unique but I didn't feel the need to remove duplicates as I didn't want to have to reason about which was the right one to remove. And I didn't feel the need to throw an error on a duplicate entry as it would be a harmless authoring error that could be intentional, (though wasted time) due to prior constraints of the project.

Anyway let me know if you have a better object structure in mind.

{
OutPropertyId = outPropertyId;
PackagesToAggregate = packagesToAggregate;
OutPropertyValue = Path.Combine(destinationRoot, string.Format("{0}.{1}", outPropertyId, GetOutputPropertyHash()));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

No more String.Format(), long live string interpolation

OutPropertyValue = Path.Combine(destinationRoot, $"{outPropertyId}.{GetOutputPropertyHash()}");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done.

}
}

public bool CreateAggregatePackage()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This method needs some logging.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yea I don't disagree with that. Is there a clean pattern you can think of to do logging with out having to make this AggregatePackage object aware of the msbuild logger by passing it in. Perhaps in this case that is a little over engineering.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should probably just move this method to the AggregatePackages task and leave the AggregatePackage class as just a data structure.


public override bool Execute()
{
BuildEngine = new CBTBuildEngine();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You shouldn't need to create a new CBTBuildEngine in this case since this code is running when the task is running as a real task. Set this on line 91 instead

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

not sure why I had that there. Thanks

BuildEngine = new CBTBuildEngine();
Dictionary<string, string> propertiesToCreate = new Dictionary<string, string>();

foreach (var aggPkg in PackagesToAggregate.Split(';'))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Be sure to ignore empty entries and trim here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the string is nullorwhitespace should handle the null,empty,and whitespace scenario all of which I am not interested in. However I have added a splitoptions to remoteemptyentries as well.

{
continue;
}
var pkg = AggregatePackage.ParseIntoPackage(aggPkg, AggregateDestRoot);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I like the TryParse pattern since its a little more standard. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

standards are good. ;)

catch (DirectoryNotFoundException exp)
{
Log.LogError(exp.Message);
throw;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should probably not throw right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

fixed

// We don't care about directories in aggregation since removing directories is not possible given current implementation.
// This is in the scenario where the user aggregates against a nuget package and a checked in folder under source control that contains a config file or whatever.
// Concern on aggregating very large folders as this is disk IO.
foreach (var f in Directory.GetFiles(pkg.Folder, "*.*", SearchOption.AllDirectories))
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Perhaps instead of by default computing the last write time on the files being aggregated we put this behind a flag that is set to off unless they enable it to true or we detect a folder to aggregate is not under the package root.

Because imagine if every project had to parse over the msbuild.corext package to compute it's hash folder.

The scenario I am getting at is to avoid incorrect hard to debug build errors.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

At the very least we should use EnumerateFiles instead of GetFiles. I definitely would like to find a more efficient way of hashing the aggregated packages. Could it just be a hash of the package names?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Actually I have an idea. I will send an update. Basically we only care about non immutable folders. So we can pass a list of immutable roots and that can be a ; delimited list of paths and by default that can be the nuget root. This would resolve the issue in the 99% case. In the 1% case the user has the escape hatch to say their immutable folder is really a non-immutable one.

@kingerja
Copy link
Copy Markdown
Contributor Author

kingerja commented May 3, 2017

I believe I have addressed your comments. There is one perf concern I have out to you for input. So please review again and take a look.

// We don't care about directories in aggregation since removing directories is not possible given current implementation.
// This is in the scenario where the user aggregates against a nuget package and a checked in folder under source control that contains a config file or whatever.
// Concern on aggregating very large folders as this is disk IO.
foreach (var f in Directory.GetFiles(pkg.Folder, "*.*", SearchOption.AllDirectories))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

At the very least we should use EnumerateFiles instead of GetFiles. I definitely would like to find a more efficient way of hashing the aggregated packages. Could it just be a hash of the package names?

throw new DirectoryNotFoundException("Source Directory does not exist or could not be found: " + sourceDirName);
}
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use EnumerateDirectories instead and EnumerateFiles instead of GetFiles below. We should probably revisit this method and see if Async copies or parallelism would help here.

project.Save(propsFile);
}

internal IEnumerable<AggregatePackage> ParsePackagesToAggregate()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should include unit tests with this pull request or open an issue to write some for this sprint.

@kingerja kingerja merged commit f98f09a into CommonBuildToolset:master May 3, 2017
@kingerja kingerja deleted the iss60 branch May 3, 2017 19:03
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants