Initial version of watch target.

- Added FileWatcher helper class
- Added ability to disable fetching k from nuget every time it's called
- Added watch target to standard goals
- Added ability to skip project generation for net45.
  This is useful for watch so that changes made to the
  project but not saved to disk won't be discarded when the
  watcher runs.
This commit is contained in:
David Fowler 2014-02-01 11:55:10 -08:00
parent 37e225d517
commit 1964e884e1
4 changed files with 196 additions and 4 deletions

122
build/FileWatcher.shade Normal file
View File

@ -0,0 +1,122 @@
use namespace="System.Collections.Concurrent"
functions
@{
public class FileWatcher
{
private readonly HashSet<string> _files = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, HashSet<string>> _directories = new ConcurrentDictionary<string, HashSet<string>>(StringComparer.OrdinalIgnoreCase);
private readonly FileSystemWatcher _watcher;
public FileWatcher(string path)
{
_watcher = new FileSystemWatcher(path);
_watcher.IncludeSubdirectories = true;
_watcher.EnableRaisingEvents = true;
_watcher.Changed += OnWatcherChanged;
_watcher.Renamed += OnRenamed;
_watcher.Deleted += OnWatcherChanged;
_watcher.Created += OnWatcherChanged;
}
public event Action<string> OnChanged;
public void WatchDirectory(string path, string extension)
{
var extensions = _directories.GetOrAdd(path, _ => new HashSet<string>(StringComparer.OrdinalIgnoreCase));
extensions.Add(extension);
}
public bool WatchFile(string path)
{
return _files.Add(path);
}
public void Dispose()
{
_watcher.Dispose();
}
public bool ReportChange(string newPath, WatcherChangeTypes changeType)
{
return ReportChange(oldPath: null, newPath: newPath, changeType: changeType);
}
public bool ReportChange(string oldPath, string newPath, WatcherChangeTypes changeType)
{
if (HasChanged(oldPath, newPath, changeType))
{
if (oldPath != null)
{
Trace.TraceInformation("{0} -> {1}", oldPath, newPath);
}
else
{
Trace.TraceInformation("{0} -> {1}", changeType, newPath);
}
if (OnChanged != null)
{
OnChanged(newPath);
}
return true;
}
return false;
}
private void OnRenamed(object sender, RenamedEventArgs e)
{
ReportChange(e.OldFullPath, e.FullPath, e.ChangeType);
}
private void OnWatcherChanged(object sender, FileSystemEventArgs e)
{
ReportChange(e.FullPath, e.ChangeType);
}
private bool HasChanged(string oldPath, string newPath, WatcherChangeTypes changeType)
{
// File changes
if (_files.Contains(newPath) ||
(oldPath != null && _files.Contains(oldPath)))
{
return true;
}
HashSet<string> extensions;
if (_directories.TryGetValue(newPath, out extensions) ||
_directories.TryGetValue(Path.GetDirectoryName(newPath), out extensions))
{
string extension = Path.GetExtension(newPath);
if (String.IsNullOrEmpty(extension))
{
// Assume it's a directory
if (changeType == WatcherChangeTypes.Created ||
changeType == WatcherChangeTypes.Renamed)
{
foreach (var e in extensions)
{
WatchDirectory(newPath, e);
}
}
else if(changeType == WatcherChangeTypes.Deleted)
{
return true;
}
// Ignore anything else
return false;
}
return extensions.Contains(extension);
}
return false;
}
}
}

View File

@ -17,11 +17,16 @@ k-generate-projects
solutionPath=''
Required. Path to the solution directory
skipNet45='false'
Optional. Skip net45 project regeneration
*/}
content var='net45' include href='net45.txt'
content var='k10' include href='k10.txt'
default skipNet45='${false}'
@{
ProjectGenerator.Logger = Log;
@ -30,6 +35,11 @@ content var='k10' include href='k10.txt'
{ "k10", k10 }
};
if(skipNet45)
{
templates.Remove("net45");
}
ProjectGenerator.MakeProjects(solutionPath, templates);
}
@ -245,9 +255,17 @@ functions
foreach (var pair in configs)
{
var targetFramework = pair.Key;
string projectTemplate;
if(!templates.TryGetValue(targetFramework, out projectTemplate))
{
Warn("Skipping project generation for " + projectName + " - " + targetFramework);
continue;
}
var allPackageReferences = new Dictionary<string, string>(packageReferences);
var targetFramework = pair.Key;
var props = (IDictionary<string, object>)pair.Value;
var specificCompilationOptions = GetObject(props, "compilationOptions") ?? compilationOptions ?? new Dictionary<string, object>();
@ -294,7 +312,7 @@ functions
}
}
var template = templates[targetFramework]
var template = projectTemplate
.Replace("{ProjectGuid}", id)
.Replace("{Name}", projectName)
.Replace("{Defines}", String.Join(";", defines))
@ -359,7 +377,7 @@ functions
"Microsoft.Net.ApplicationHost.dll");
var klrSwitches = configType == "k10" ? "--core45" : "--net45";
var args = klrSwitches + " " + appHostPath + " " + projectRoot + " --nobin";
var args = klrSwitches + " " + appHostPath + " --nobin " + projectRoot;
return String.Format(@"<StartAction>Program</StartAction>
<StartProgram>{0}</StartProgram>

View File

@ -2,6 +2,7 @@ use namespace="System"
use namespace="System.IO"
use import="Files"
use import="BuildEnv"
use import="FileWatcher"
default BASE_DIR='${Directory.GetCurrentDirectory()}'
default TARGET_DIR='${Path.Combine(BASE_DIR, "artifacts")}'
@ -34,7 +35,53 @@ default TEST_DIR='${Path.Combine(TARGET_DIR, "test")}'
#nuget-install target='install' description='Copy NuGet packages to local repo'
nuget-local-publish sourcePackagesDir='${BUILD_DIR}'
#watch
@{
var watcher = new FileWatcher(BASE_DIR);
foreach (var file in Directory.EnumerateFiles(BASE_DIR, "*.json", SearchOption.AllDirectories))
{
watcher.WatchFile(file);
}
foreach (var file in Directory.EnumerateFiles(BASE_DIR, "*.cs", SearchOption.AllDirectories))
{
watcher.WatchFile(file);
}
foreach (var dir in Directory.EnumerateDirectories(BASE_DIR, "*.*", SearchOption.AllDirectories))
{
watcher.WatchDirectory(dir, ".cs");
watcher.WatchDirectory(dir, ".json");
}
watcher.OnChanged += path =>
{
Log.Info("Change detected in " + path);
bool restore = path.EndsWith("project.json");
try
{
UpdateProjects(BASE_DIR, restore);
}
catch (Exception ex)
{
Log.Warn(ex.Message);
}
Log.Info("Waiting for changes...");
};
Log.Info("Waiting for changes...");
Console.ReadLine();
}
functions @{
string E(string key) { return Environment.GetEnvironmentVariable(key); }
void E(string key, string value) { Environment.SetEnvironmentVariable(key, value); }
}
macro name='UpdateProjects' basePath='string' restore='bool'
k command='restore' prefetch='${false}' if='restore'
k-generate-projects solutionPath='${basePath}' skipNet45='${!restore}'

View File

@ -11,9 +11,14 @@ kProgram='...'
command=''
prefetch='true'
May be passed if fetching k from nuget is required before running
*/}
nuget-install package='ProjectK' outputDir='packages' extra='-pre' once='ProjectK-NuGet'
default prefetch='${true}'
nuget-install package='ProjectK' outputDir='packages' extra='-pre' once='ProjectK-NuGet' if='prefetch'
@{
Func<string, long> getVersion = version => {