ProjectContext.MakeRunnable

PR Feedback

Fixing naming of GetContentFiles
This commit is contained in:
piotrp 2016-01-04 12:49:13 -08:00
parent a20cffb9e3
commit ef0fc05119
12 changed files with 374 additions and 219 deletions

View file

@ -12,29 +12,29 @@ using System.Text;
using System.Xml.Linq;
using Microsoft.DotNet.ProjectModel.Compilation;
namespace Microsoft.DotNet.Tools.Compiler
namespace Microsoft.DotNet.Cli.Utils
{
internal class BindingRedirectGenerator
internal static class BindingRedirectGenerator
{
private const int TokenLength = 8;
private const string Namespace = "urn:schemas-microsoft-com:asm.v1";
private static XName ConfigurationElementName = XName.Get("configuration");
private static XName RuntimeElementName = XName.Get("runtime");
private static XName AssemblyBindingElementName = XName.Get("assemblyBinding", Namespace);
private static XName DependentAssemblyElementName = XName.Get("dependentAssembly", Namespace);
private static XName AssemblyIdentityElementName = XName.Get("assemblyIdentity", Namespace);
private static XName BindingRedirectElementName = XName.Get("bindingRedirect", Namespace);
private static readonly XName ConfigurationElementName = XName.Get("configuration");
private static readonly XName RuntimeElementName = XName.Get("runtime");
private static readonly XName AssemblyBindingElementName = XName.Get("assemblyBinding", Namespace);
private static readonly XName DependentAssemblyElementName = XName.Get("dependentAssembly", Namespace);
private static readonly XName AssemblyIdentityElementName = XName.Get("assemblyIdentity", Namespace);
private static readonly XName BindingRedirectElementName = XName.Get("bindingRedirect", Namespace);
private static XName NameAttributeName = XName.Get("name");
private static XName PublicKeyTokenAttributeName = XName.Get("publicKeyToken");
private static XName CultureAttributeName = XName.Get("culture");
private static XName OldVersionAttributeName = XName.Get("oldVersion");
private static XName NewVersionAttributeName = XName.Get("newVersion");
private static readonly XName NameAttributeName = XName.Get("name");
private static readonly XName PublicKeyTokenAttributeName = XName.Get("publicKeyToken");
private static readonly XName CultureAttributeName = XName.Get("culture");
private static readonly XName OldVersionAttributeName = XName.Get("oldVersion");
private static readonly XName NewVersionAttributeName = XName.Get("newVersion");
private readonly SHA1 _sha1 = SHA1.Create();
internal static SHA1 Sha1 { get; } = SHA1.Create();
public XDocument Generate(IEnumerable<LibraryExport> dependencies, XDocument document)
internal static XDocument GenerateBindingRedirects(this IEnumerable<LibraryExport> dependencies, XDocument document)
{
var redirects = CollectRedirects(dependencies);
@ -57,7 +57,7 @@ namespace Microsoft.DotNet.Tools.Compiler
return document;
}
private void AddDependentAssembly(AssemblyRedirect redirect, XElement assemblyBindings)
private static void AddDependentAssembly(AssemblyRedirect redirect, XElement assemblyBindings)
{
var dependencyElement = assemblyBindings.Elements(DependentAssemblyElementName)
.FirstOrDefault(element => IsSameAssembly(redirect, element));
@ -80,16 +80,16 @@ namespace Microsoft.DotNet.Tools.Compiler
));
}
private bool IsSameAssembly(AssemblyRedirect redirect, XElement dependentAssemblyElement)
private static bool IsSameAssembly(AssemblyRedirect redirect, XElement dependentAssemblyElement)
{
var identity = dependentAssemblyElement.Element(AssemblyIdentityElementName);
if (identity == null)
{
return false;
}
return (string) identity.Attribute(NameAttributeName) == redirect.From.Name &&
(string) identity.Attribute(PublicKeyTokenAttributeName) == redirect.From.PublicKeyToken &&
(string) identity.Attribute(CultureAttributeName) == redirect.From.Culture;
return (string)identity.Attribute(NameAttributeName) == redirect.From.Name &&
(string)identity.Attribute(PublicKeyTokenAttributeName) == redirect.From.PublicKeyToken &&
(string)identity.Attribute(CultureAttributeName) == redirect.From.Culture;
}
public static XElement GetOrAddElement(XContainer parent, XName elementName)
@ -107,7 +107,7 @@ namespace Microsoft.DotNet.Tools.Compiler
return element;
}
private AssemblyRedirect[] CollectRedirects(IEnumerable<LibraryExport> dependencies)
private static AssemblyRedirect[] CollectRedirects(IEnumerable<LibraryExport> dependencies)
{
var allRuntimeAssemblies = dependencies.SelectMany(d => d.RuntimeAssemblies).Select(GetAssemblyInfo).ToArray();
var assemblyLookup = allRuntimeAssemblies.ToDictionary(r => r.Identity.ToLookupKey());
@ -136,14 +136,14 @@ namespace Microsoft.DotNet.Tools.Compiler
return redirectAssemblies.ToArray();
}
private AssemblyReferenceInfo GetAssemblyInfo(LibraryAsset arg)
private static AssemblyReferenceInfo GetAssemblyInfo(LibraryAsset arg)
{
using (var peReader = new PEReader(File.OpenRead(arg.ResolvedPath)))
{
var metadataReader = peReader.GetMetadataReader();
var definition = metadataReader.GetAssemblyDefinition();
var identity = new AssemblyIdentity(
metadataReader.GetString(definition.Name),
definition.Version,
@ -168,7 +168,7 @@ namespace Microsoft.DotNet.Tools.Compiler
}
}
private string GetPublicKeyToken(byte[] bytes)
private static string GetPublicKeyToken(byte[] bytes)
{
if (bytes.Length == 0)
{
@ -183,7 +183,7 @@ namespace Microsoft.DotNet.Tools.Compiler
else
{
token = new byte[TokenLength];
var sha1 = _sha1.ComputeHash(bytes);
var sha1 = Sha1.ComputeHash(bytes);
Array.Copy(sha1, sha1.Length - TokenLength, token, 0, TokenLength);
Array.Reverse(token);
}
@ -215,7 +215,7 @@ namespace Microsoft.DotNet.Tools.Compiler
{
Name = name;
Version = version;
Culture = string.IsNullOrEmpty(culture)? "neutral" : culture;
Culture = string.IsNullOrEmpty(culture) ? "neutral" : culture;
PublicKeyToken = publicKeyToken;
}

View file

@ -141,7 +141,7 @@ namespace Microsoft.DotNet.Cli.Utils
return fileNames.Contains(commandName + FileNameSuffixes.DotNet.Exe) &&
fileNames.Contains(commandName + FileNameSuffixes.DotNet.DynamicLib) &&
fileNames.Contains(commandName + FileNameSuffixes.DotNet.Deps);
fileNames.Contains(commandName + FileNameSuffixes.Deps);
});
if (commandPackage == null) return null;

View file

@ -0,0 +1,30 @@
using System.IO;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Cli.Utils
{
internal static class CoreHost
{
internal static string _path;
public static string FileName = "corehost" + FileNameSuffixes.CurrentPlatform.Exe;
public static string Path
{
get
{
if (_path == null)
{
_path = Env.GetCommandPath(FileName, new[] {string.Empty});
}
return _path;
}
}
public static void CopyTo(string destinationPath)
{
File.Copy(Path, destinationPath, overwrite: true);
}
}
}

View file

@ -0,0 +1,20 @@
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.DotNet.Cli.Utils
{
internal static class CsvFormatter
{
internal static string EscapeRow(IEnumerable<string> values)
{
return values
.Select(EscapeValue)
.Aggregate((a, v) => a + "," + v);
}
internal static string EscapeValue(string value)
{
return "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
}
}
}

View file

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Microsoft.DotNet.Cli.Utils
{
internal static class Env
{
private static IEnumerable<string> _searchPaths;
private static IEnumerable<string> _executableExtensions;
public static IEnumerable<string> ExecutableExtensions
{
get
{
if (_executableExtensions == null)
{
_executableExtensions = RuntimeInformation.IsOSPlatform(OSPlatform.Windows)
? Environment.GetEnvironmentVariable("PATHEXT").Split(';').Select(e => e.ToLower())
: new [] { string.Empty };
}
return _executableExtensions;
}
}
private static IEnumerable<string> SearchPaths
{
get
{
if (_searchPaths == null)
{
var searchPaths = new List<string> {AppContext.BaseDirectory};
searchPaths.AddRange(Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator));
_searchPaths = searchPaths;
}
return _searchPaths;
}
}
public static string GetCommandPath(string commandName, params string[] extensions)
{
if (!extensions.Any())
extensions = Env.ExecutableExtensions.ToArray();
var commandPath = Env.SearchPaths.Join(
extensions,
p => true, s => true,
(p, s) => Path.Combine(p, commandName + s))
.FirstOrDefault(File.Exists);
return commandPath;
}
}
}

View file

@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.DotNet.ProjectModel.Compilation;
using System.Linq;
using Microsoft.DotNet.ProjectModel;
namespace Microsoft.DotNet.Cli.Utils
{
internal static class LibraryExporterExtensions
{
internal static void CopyProjectDependenciesTo(this LibraryExporter exporter, string path, params ProjectDescription[] except)
{
exporter.GetAllExports()
.Where(e => !except.Contains(e.Library))
.Where(e => e.Library is ProjectDescription)
.SelectMany(e => e.NativeLibraries.Union(e.RuntimeAssemblies))
.CopyTo(path);
}
internal static void WriteDepsTo(this IEnumerable<LibraryExport> exports, string path)
{
File.WriteAllLines(path, exports.SelectMany(GenerateLines));
}
private static IEnumerable<string> GenerateLines(LibraryExport export)
{
return GenerateLines(export, export.RuntimeAssemblies, "runtime")
.Union(GenerateLines(export, export.NativeLibraries, "native"));
}
private static IEnumerable<string> GenerateLines(LibraryExport export, IEnumerable<LibraryAsset> items, string type)
{
return items.Select(i => CsvFormatter.EscapeRow(new[]
{
export.Library.Identity.Type.Value,
export.Library.Identity.Name,
export.Library.Identity.Version.ToNormalizedString(),
export.Library.Hash,
type,
i.Name,
i.RelativePath
}));
}
internal static IEnumerable<LibraryAsset> RuntimeAssets(this LibraryExport export)
{
return export.RuntimeAssemblies.Union(export.NativeLibraries);
}
internal static void CopyTo(this IEnumerable<LibraryAsset> assets, string destinationPath)
{
foreach (var asset in assets)
{
File.Copy(asset.ResolvedPath, Path.Combine(destinationPath, Path.GetFileName(asset.ResolvedPath)),
overwrite: true);
}
}
}
}

View file

@ -3,7 +3,6 @@
using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace Microsoft.DotNet.Tools.Common
@ -199,5 +198,17 @@ namespace Microsoft.DotNet.Tools.Common
return GetPathWithBackSlashes(path);
}
}
public static bool HasExtension(string filePath, string extension)
{
var comparison = StringComparison.Ordinal;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
comparison = StringComparison.OrdinalIgnoreCase;
}
return Path.GetExtension(filePath).Equals(extension, comparison);
}
}
}

View file

@ -2,8 +2,14 @@
// 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.Xml.Linq;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.Tools.Common;
using NuGet.Frameworks;
namespace Microsoft.DotNet.Cli.Utils
@ -63,5 +69,118 @@ namespace Microsoft.DotNet.Cli.Utils
return rootOutputPath;
}
internal static void MakeCompilationOutputRunnable(this ProjectContext context, string outputPath, string configuration)
{
context
.ProjectFile
.Files
.GetContentFiles()
.StructuredCopyTo(context.ProjectDirectory, outputPath)
.RemoveAttribute(FileAttributes.ReadOnly);
var exporter = context.CreateExporter(configuration);
if (context.TargetFramework.IsDesktop())
{
exporter
.GetDependencies()
.SelectMany(e => e.RuntimeAssets())
.CopyTo(outputPath);
GenerateBindingRedirects(context, outputPath, configuration);
}
else
{
exporter
.GetDependencies(LibraryType.Package)
.WriteDepsTo(Path.Combine(outputPath, context.ProjectFile.Name + FileNameSuffixes.Deps));
exporter.GetDependencies(LibraryType.Project)
.SelectMany(e => e.RuntimeAssets())
.CopyTo(outputPath);
CoreHost.CopyTo(Path.Combine(outputPath, context.ProjectFile.Name + Constants.ExeSuffix));
}
}
private static IEnumerable<string> StructuredCopyTo(this IEnumerable<string> sourceFiles, string sourceDirectory, string targetDirectory)
{
if (sourceFiles == null)
{
throw new ArgumentNullException(nameof(sourceFiles));
}
var pathMap = sourceFiles
.ToDictionary(s => s, s => Path.Combine(
targetDirectory,
PathUtility.GetRelativePath(sourceDirectory, s)));
foreach (var targetDir in pathMap.Values
.Select(Path.GetDirectoryName)
.Distinct()
.Where(t => !Directory.Exists(t)))
{
Directory.CreateDirectory(targetDir);
}
foreach (var sourceFilePath in pathMap.Keys)
{
File.Copy(
sourceFilePath,
pathMap[sourceFilePath],
overwrite: true);
}
return pathMap.Values;
}
private static IEnumerable<string> RemoveAttribute(this IEnumerable<string> files, FileAttributes attribute)
{
foreach (var file in files)
{
var fileAttributes = File.GetAttributes(file);
if ((fileAttributes & attribute) == attribute)
{
File.SetAttributes(file, fileAttributes & ~attribute);
}
}
return files;
}
private static void GenerateBindingRedirects(this ProjectContext context, string outputPath, string configuration)
{
var existingConfig = new DirectoryInfo(context.ProjectDirectory)
.EnumerateFiles()
.FirstOrDefault(f => f.Name.Equals("app.config", StringComparison.OrdinalIgnoreCase));
XDocument baseAppConfig = null;
if (existingConfig != null)
{
using (var fileStream = File.OpenRead(existingConfig.FullName))
{
baseAppConfig = XDocument.Load(fileStream);
}
}
var appConfig = context.CreateExporter(configuration).GetAllExports().GenerateBindingRedirects(baseAppConfig);
if (appConfig == null) return;
var path = Path.Combine(outputPath, context.ProjectFile.Name + ".exe.config");
using (var stream = File.Create(path))
{
appConfig.Save(stream);
}
}
public static string GetDepsPath(this ProjectContext context, string buildConfiguration)
{
return Path.Combine(context.GetOutputDirectoryPath(buildConfiguration), context.ProjectFile.Name + ".deps");
}
}
}

View file

@ -1,14 +1,15 @@
{
"version": "1.0.0-*",
"version": "1.0.0-*",
"shared": "**/*.cs",
"shared": "**/*.cs",
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616",
"Microsoft.DotNet.ProjectModel": "1.0.0"
},
"dependencies": {
"NETStandard.Library": "1.0.0-rc2-23616",
"Microsoft.DotNet.ProjectModel": "1.0.0",
"System.Reflection.Metadata": "1.1.0"
},
"frameworks": {
"dnxcore50": { }
}
"frameworks": {
"dnxcore50": { }
}
}

View file

@ -1,16 +1,30 @@
using System;
using System.Runtime.InteropServices;
namespace Microsoft.DotNet.ProjectModel
{
public static class FileNameSuffixes
{
public const string Deps = ".deps";
public static PlatformFileNameSuffixes CurrentPlatform
{
get
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) return Windows;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) return Linux;
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) return OSX;
throw new InvalidOperationException("Unknown Platform");
}
}
public static PlatformFileNameSuffixes DotNet { get; } = new PlatformFileNameSuffixes
{
DynamicLib = ".dll",
Exe = ".exe",
ProgramDatabase = ".pdb",
StaticLib = ".lib",
Deps = ".deps",
StaticLib = ".lib"
};
public static PlatformFileNameSuffixes Windows { get; } = new PlatformFileNameSuffixes
@ -18,8 +32,7 @@ namespace Microsoft.DotNet.ProjectModel
DynamicLib = ".dll",
Exe = ".exe",
ProgramDatabase = ".pdb",
StaticLib = ".lib",
Deps = ".deps",
StaticLib = ".lib"
};
public static PlatformFileNameSuffixes OSX { get; } = new PlatformFileNameSuffixes
@ -27,21 +40,26 @@ namespace Microsoft.DotNet.ProjectModel
DynamicLib = ".dylib",
Exe = "",
ProgramDatabase = ".pdb",
StaticLib = ".a",
Deps = ".deps"
StaticLib = ".a"
};
public static PlatformFileNameSuffixes Linux { get; } = new PlatformFileNameSuffixes
{
DynamicLib = ".so",
Exe = "",
ProgramDatabase = ".pdb",
StaticLib = ".a"
};
public struct PlatformFileNameSuffixes
{
public string DynamicLib { get; set; }
public string DynamicLib { get; internal set; }
public string Exe { get; set; }
public string Exe { get; internal set; }
public string ProgramDatabase { get; set; }
public string ProgramDatabase { get; internal set; }
public string StaticLib { get; set; }
public string Deps { get; set; }
public string StaticLib { get; internal set; }
}
}
}

View file

@ -138,7 +138,7 @@ namespace Microsoft.DotNet.ProjectModel.Files
get { return SharedPatternsGroup.SearchFiles(_projectDirectory).Distinct(); }
}
public IEnumerable<string> GetCopyToOutputFiles(IEnumerable<string> additionalExcludePatterns = null)
public IEnumerable<string> GetContentFiles(IEnumerable<string> additionalExcludePatterns = null)
{
var patternGroup = new PatternGroup(ContentPatternsGroup.IncludePatterns,
ContentPatternsGroup.ExcludePatterns.Concat(additionalExcludePatterns ?? new List<string>()),

View file

@ -14,6 +14,7 @@ using Microsoft.DotNet.Cli.Compiler.Common;
using Microsoft.DotNet.Tools.Common;
using Microsoft.DotNet.ProjectModel;
using Microsoft.DotNet.ProjectModel.Compilation;
using Microsoft.DotNet.ProjectModel.Graph;
using Microsoft.DotNet.ProjectModel.Utilities;
using NuGet.Frameworks;
using Microsoft.Extensions.DependencyModel;
@ -348,9 +349,9 @@ namespace Microsoft.DotNet.Tools.Compiler
if (success && !args.NoHostValue && compilationOptions.EmitEntryPoint.GetValueOrDefault())
{
MakeRunnable(runtimeContext,
outputPath,
libraryExporter);
var projectContext = ProjectContext.Create(context.ProjectDirectory, context.TargetFramework, new[] { RuntimeIdentifier.Current });
projectContext
.MakeCompilationOutputRunnable(outputPath, args.ConfigValue);
}
return PrintSummary(diagnostics, sw, success);
@ -380,135 +381,13 @@ namespace Microsoft.DotNet.Tools.Compiler
return Path.Combine(outputPath, project.Name + outputExtension);
}
private static void CleanOrCreateDirectory(string path)
{
if (Directory.Exists(path))
{
try
{
Directory.Delete(path, recursive: true);
}
catch (Exception e)
{
Console.WriteLine("Unable to remove directory: " + path);
Console.WriteLine(e.Message);
}
}
Directory.CreateDirectory(path);
}
private static void MakeRunnable(ProjectContext runtimeContext, string outputPath, LibraryExporter exporter)
{
CopyContents(runtimeContext, outputPath);
if (runtimeContext.TargetFramework.IsDesktop())
{
// On desktop we need to copy dependencies since we don't own the host
foreach (var export in exporter.GetDependencies())
{
CopyExport(outputPath, export);
}
GenerateBindingRedirects(runtimeContext, outputPath, exporter);
}
else
{
EmitHost(runtimeContext, outputPath, exporter);
}
}
private static void GenerateBindingRedirects(ProjectContext runtimeContext, string outputPath, LibraryExporter exporter)
{
var appConfigNames = new[] { "app.config", "App.config" };
XDocument baseAppConfig = null;
foreach (var appConfigName in appConfigNames)
{
var baseAppConfigPath = Path.Combine(runtimeContext.ProjectDirectory, appConfigName);
if (File.Exists(baseAppConfigPath))
{
using (var fileStream = File.OpenRead(baseAppConfigPath))
{
baseAppConfig = XDocument.Load(fileStream);
break;
}
}
}
var generator = new BindingRedirectGenerator();
var appConfig = generator.Generate(exporter.GetAllExports(), baseAppConfig);
if (appConfig != null)
{
var path = Path.Combine(outputPath, runtimeContext.ProjectFile.Name + ".exe.config");
using (var stream = File.Create(path))
{
appConfig.Save(stream);
}
}
}
private static void CopyExport(string outputPath, LibraryExport export)
{
CopyFiles(export.RuntimeAssemblies, outputPath);
CopyFiles(export.NativeLibraries, outputPath);
}
private static void EmitHost(ProjectContext runtimeContext, string outputPath, LibraryExporter exporter)
{
// Write the Host information file (basically a simplified form of the lock file)
var lines = new List<string>();
foreach (var export in exporter.GetAllExports())
{
if (export.Library == runtimeContext.RootProject)
{
continue;
}
if (export.Library is ProjectDescription)
{
// Copy project dependencies to the output folder
CopyFiles(export.RuntimeAssemblies, outputPath);
CopyFiles(export.NativeLibraries, outputPath);
}
else
{
lines.AddRange(GenerateLines(export, export.RuntimeAssemblies, "runtime"));
lines.AddRange(GenerateLines(export, export.NativeLibraries, "native"));
}
}
File.WriteAllLines(Path.Combine(outputPath, runtimeContext.ProjectFile.Name + ".deps"), lines);
// Copy the host in
CopyHost(Path.Combine(outputPath, runtimeContext.ProjectFile.Name + Constants.ExeSuffix));
}
private static void CopyHost(string target)
{
var hostPath = Path.Combine(AppContext.BaseDirectory, Constants.HostExecutableName);
File.Copy(hostPath, target, overwrite: true);
}
private static IEnumerable<string> GenerateLines(LibraryExport export, IEnumerable<LibraryAsset> items, string type)
{
return items.Select(item =>
EscapeCsv(export.Library.Identity.Type.Value) + "," +
EscapeCsv(export.Library.Identity.Name) + "," +
EscapeCsv(export.Library.Identity.Version.ToNormalizedString()) + "," +
EscapeCsv(export.Library.Hash) + "," +
EscapeCsv(type) + "," +
EscapeCsv(item.Name) + "," +
EscapeCsv(item.RelativePath) + ",");
}
private static string EscapeCsv(string input)
{
return "\"" + input.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
}
private static bool PrintSummary(List<DiagnosticMessage> diagnostics, Stopwatch sw, bool success = true)
{
PrintDiagnostics(diagnostics);
@ -663,49 +542,6 @@ namespace Microsoft.DotNet.Tools.Compiler
}
}
private static void CopyContents(ProjectContext context, string outputPath)
{
var sourceFiles = context.ProjectFile.Files.GetCopyToOutputFiles();
Copy(sourceFiles, context.ProjectDirectory, outputPath);
}
private static void Copy(IEnumerable<string> sourceFiles, string sourceDirectory, string targetDirectory)
{
if (sourceFiles == null)
{
throw new ArgumentNullException(nameof(sourceFiles));
}
sourceDirectory = EnsureTrailingSlash(sourceDirectory);
targetDirectory = EnsureTrailingSlash(targetDirectory);
foreach (var sourceFilePath in sourceFiles)
{
var fileName = Path.GetFileName(sourceFilePath);
var targetFilePath = sourceFilePath.Replace(sourceDirectory, targetDirectory);
var targetFileParentFolder = Path.GetDirectoryName(targetFilePath);
// Create directory before copying a file
if (!Directory.Exists(targetFileParentFolder))
{
Directory.CreateDirectory(targetFileParentFolder);
}
File.Copy(
sourceFilePath,
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 string EnsureTrailingSlash(string path)
{
return EnsureTrailingCharacter(path, Path.DirectorySeparatorChar);