Merge pull request #4020 from dotnet/piotrpMSFT/msbuild872

Move CLI Build to MSBuild 00024-160610
This commit is contained in:
Piotr Puszkiewicz 2016-08-09 13:10:50 -04:00 committed by GitHub
commit 912a47f672
21 changed files with 1203 additions and 23 deletions

3
.gitignore vendored
View file

@ -186,9 +186,6 @@ DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml

View file

@ -122,6 +122,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{89905EC4
build\Microsoft.DotNet.Cli.Monikers.props = build\Microsoft.DotNet.Cli.Monikers.props
build\Microsoft.DotNet.Cli.Package.targets = build\Microsoft.DotNet.Cli.Package.targets
build\Microsoft.DotNet.Cli.Prepare.targets = build\Microsoft.DotNet.Cli.Prepare.targets
build\Microsoft.DotNet.Cli.Publish.targets = build\Microsoft.DotNet.Cli.Publish.targets
build\Microsoft.DotNet.Cli.Run.targets = build\Microsoft.DotNet.Cli.Run.targets
build\Microsoft.DotNet.Cli.tasks = build\Microsoft.DotNet.Cli.tasks
build\Microsoft.DotNet.Cli.Test.targets = build\Microsoft.DotNet.Cli.Test.targets
EndProjectSection
EndProject
@ -153,6 +156,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Tools.Test
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.DotNet.Core.Build.Tasks.Tests", "test\Microsoft.DotNet.Core.Build.Tasks.Tests\Microsoft.DotNet.Core.Build.Tasks.Tests.xproj", "{EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "publish", "publish", "{27B12960-ABB0-4903-9C60-5E9157E659C8}"
ProjectSection(SolutionItems) = preProject
build\publish\Microsoft.DotNet.Cli.Badge.targets = build\publish\Microsoft.DotNet.Cli.Badge.targets
build\publish\PublishContent.targets = build\publish\PublishContent.targets
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -994,5 +1003,6 @@ Global
{E8E7D24B-4830-4662-80A8-255D6FE3B0BE} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F}
{E4F46EAB-B5A5-4E60-9B9D-40A1FADBF45C} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{EDD6C92D-0A58-4FCB-A0E9-9D0FFC045177} = {17735A9D-BFD9-4585-A7CB-3208CA6EA8A7}
{27B12960-ABB0-4903-9C60-5E9157E659C8} = {89905EC4-BC0F-443B-8ADF-691321F10108}
EndGlobalSection
EndGlobal

View file

@ -18,8 +18,6 @@
<RepoRoot>$(MSBuildThisFileDirectory)</RepoRoot>
<CLIBuildFileName>$(RepoRoot)/build_projects/dotnet-cli-build/bin/dotnet-cli-build</CLIBuildFileName>
<CLIBuildDll>$(CLIBuildFileName).dll</CLIBuildDll>
<BuildToolsDir>$(RepoRoot)/build_tools/</BuildToolsDir>
<ToolsDir>$(BuildToolsDir)/</ToolsDir>
<CoreSetupChannel>preview</CoreSetupChannel>
<SharedFrameworkName>Microsoft.NETCore.App</SharedFrameworkName>
@ -81,12 +79,10 @@
<Target DependsOnTargets="$(CLITargets)" Name="BuildTheWholeCli"></Target>
<Import Project="$(BuildToolsDir)/Build.Common.targets" />
<Import Project="build/Microsoft.DotNet.Cli.Prepare.targets" />
<Import Project="build/Microsoft.DotNet.Cli.Compile.targets" />
<Import Project="build/Microsoft.DotNet.Cli.Package.targets" />
<Import Project="build/Microsoft.DotNet.Cli.Test.targets" />
<Import Project="build/Microsoft.DotNet.Cli.Publish.targets" />
<Import Project="build/Microsoft.DotNet.Cli.Run.targets" />
<Import Project="$(BuildToolsDir)/clean.targets" />
</Project>

View file

@ -210,6 +210,10 @@
<Copy SourceFiles="@(MSBuildTargetsToCopy)"
DestinationFiles="@(MSBuildTargetsToCopy->'$(SdkOutputDirectory)\%(RecursiveDir)%(Filename)%(Extension)')" />
<!-- Temporary workaround for MSBuild placing their .props files in the old 14.1 directory -->
<Move SourceFiles="$(SdkOutputDirectory)/14.1/Microsoft.Common.props"
DestinationFolder="$(SdkOutputDirectory)/15.0" />
<ItemGroup>
<FilesToClean Include="$(StageDirectory)/sdk/**/vbc.exe" />
</ItemGroup>

View file

@ -1,12 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(BuildToolsDir)PublishContent.targets" />
<Import Project="$(MSBuildThisFileDirectory)/publish/PublishContent.targets" />
<Import Project="$(MSBuildThisFileDirectory)/publish/Microsoft.DotNet.Cli.Badge.targets" />
<UsingTask TaskName="UploadToAzure" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
<UsingTask TaskName="FinalizeBuild" AssemblyFile="$(CLIBuildDll)" />
<UsingTask TaskName="SetBlobPropertiesBasedOnFileType" AssemblyFile="$(CLIBuildDll)" />
<!-- PUBLISH_TO_AZURE_BLOB env variable set by CI -->
<Target Name="Publish"
Condition=" '$(PUBLISH_TO_AZURE_BLOB)' != '' "

View file

@ -23,7 +23,8 @@
<UsingTask TaskName="SetEnvVar" AssemblyFile="$(CLIBuildDll)" />
<UsingTask TaskName="TarGzFileCreateFromDirectory" AssemblyFile="$(CLIBuildDll)" />
<UsingTask TaskName="TarGzFileExtractToDirectory" AssemblyFile="$(CLIBuildDll)" />
<UsingTask TaskName="UploadToAzure" AssemblyFile="$(BuildToolsTaskDir)Microsoft.DotNet.Build.CloudTestTasks.dll"/>
<UsingTask TaskName="ZipFileCreateFromDirectory" AssemblyFile="$(BuildToolsDir)Microsoft.DotNet.Build.Tasks.dll"/>
<UsingTask TaskName="ZipFileExtractToDirectory" AssemblyFile="$(BuildToolsDir)Microsoft.DotNet.Build.Tasks.dll"/>
<UsingTask TaskName="CreateAzureContainer" AssemblyFile="$(CLIBuildDll)"/>
<UsingTask TaskName="UploadToAzure" AssemblyFile="$(CLIBuildDll)"/>
<UsingTask TaskName="ZipFileCreateFromDirectory" AssemblyFile="$(CLIBuildDll)"/>
<UsingTask TaskName="ZipFileExtractToDirectory" AssemblyFile="$(CLIBuildDll)"/>
</Project>

View file

@ -0,0 +1,41 @@
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OverwriteOnPublish Condition="'$(OverwriteOnPublish)' == ''">false</OverwriteOnPublish>
</PropertyGroup>
<!-- gathers the items to be published -->
<Target Name="GatherItemsForPattern">
<Error Condition="'$(PublishPattern)' == ''" Text="Please specify a value for PublishPattern using standard msbuild 'include' syntax." />
<ItemGroup>
<ForPublishing Include="$(PublishPattern)" />
</ItemGroup>
<!-- add relative blob path metadata -->
<ItemGroup>
<ForPublishing>
<RelativeBlobPath>$([System.String]::Copy('%(RecursiveDir)%(Filename)%(Extension)').Replace('\' ,'/'))</RelativeBlobPath>
</ForPublishing>
</ItemGroup>
<Error Condition="'@(ForPublishing)' == ''" Text="No items were found matching pattern '$(PublishPattern)'." />
</Target>
<!-- publishes items to blob storage in Azure -->
<Target Name="UploadToAzure" DependsOnTargets="GatherItemsForPattern">
<Error Condition="'$(ContainerName)' == ''" Text="Missing property ContainerName." />
<Error Condition="'$(CloudDropAccountName)' == ''" Text="Missing property CloudDropAccountName." />
<Error Condition="'$(CloudDropAccessToken)' == ''" Text="Missing property CloudDropAccessToken." />
<!-- create the container if it doesn't exist -->
<CreateAzureContainer
AccountKey="$(CloudDropAccessToken)"
AccountName="$(CloudDropAccountName)"
ContainerName="$(ContainerName)" />
<!-- now upload the items -->
<UploadToAzure
AccountKey="$(CloudDropAccessToken)"
AccountName="$(CloudDropAccountName)"
ContainerName="$(ContainerName)"
Items="@(ForPublishing)"
Overwrite="$(OverwriteOnPublish)" />
</Target>
</Project>

View file

@ -0,0 +1,401 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
namespace Microsoft.DotNet.Build.CloudTestTasks
{
public static class AzureHelper
{
/// <summary>
/// The storage api version.
/// </summary>
public static readonly string StorageApiVersion = "2015-04-05";
public const string DateHeaderString = "x-ms-date";
public const string VersionHeaderString = "x-ms-version";
public const string AuthorizationHeaderString = "Authorization";
public enum SasAccessType
{
Read,
Write,
};
public static string AuthorizationHeader(
string storageAccount,
string storageKey,
string method,
DateTime now,
HttpRequestMessage request,
string ifMatch = "",
string contentMD5 = "",
string size = "",
string contentType = "")
{
string stringToSign = string.Format(
"{0}\n\n\n{1}\n{5}\n{6}\n\n\n{2}\n\n\n\n{3}{4}",
method,
(size == string.Empty) ? string.Empty : size,
ifMatch,
GetCanonicalizedHeaders(request),
GetCanonicalizedResource(request.RequestUri, storageAccount),
contentMD5,
contentType);
byte[] signatureBytes = Encoding.UTF8.GetBytes(stringToSign);
string authorizationHeader;
using (HMACSHA256 hmacsha256 = new HMACSHA256(Convert.FromBase64String(storageKey)))
{
authorizationHeader = "SharedKey " + storageAccount + ":"
+ Convert.ToBase64String(hmacsha256.ComputeHash(signatureBytes));
}
return authorizationHeader;
}
public static string CreateContainerSasToken(
string accountName,
string containerName,
string key,
SasAccessType accessType,
int validityTimeInDays)
{
string signedPermissions = string.Empty;
switch (accessType)
{
case SasAccessType.Read:
signedPermissions = "r";
break;
case SasAccessType.Write:
signedPermissions = "wdl";
break;
default:
throw new ArgumentOutOfRangeException(nameof(accessType), accessType, "Unrecognized value");
}
string signedStart = DateTime.UtcNow.ToString("O");
string signedExpiry = DateTime.UtcNow.AddDays(validityTimeInDays).ToString("O");
string canonicalizedResource = "/blob/" + accountName + "/" + containerName;
string signedIdentifier = string.Empty;
string signedVersion = StorageApiVersion;
string stringToSign = ConstructServiceStringToSign(
signedPermissions,
signedVersion,
signedExpiry,
canonicalizedResource,
signedIdentifier,
signedStart);
byte[] signatureBytes = Encoding.UTF8.GetBytes(stringToSign);
string signature;
using (HMACSHA256 hmacSha256 = new HMACSHA256(Convert.FromBase64String(key)))
{
signature = Convert.ToBase64String(hmacSha256.ComputeHash(signatureBytes));
}
string sasToken = string.Format(
"?sv={0}&sr={1}&sig={2}&st={3}&se={4}&sp={5}",
WebUtility.UrlEncode(signedVersion),
WebUtility.UrlEncode("c"),
WebUtility.UrlEncode(signature),
WebUtility.UrlEncode(signedStart),
WebUtility.UrlEncode(signedExpiry),
WebUtility.UrlEncode(signedPermissions));
return sasToken;
}
public static string GetCanonicalizedHeaders(HttpRequestMessage request)
{
StringBuilder sb = new StringBuilder();
List<string> headerNameList = (from headerName in request.Headers
where
headerName.Key.ToLowerInvariant()
.StartsWith("x-ms-", StringComparison.Ordinal)
select headerName.Key.ToLowerInvariant()).ToList();
headerNameList.Sort();
foreach (string headerName in headerNameList)
{
StringBuilder builder = new StringBuilder(headerName);
string separator = ":";
foreach (string headerValue in GetHeaderValues(request.Headers, headerName))
{
string trimmedValue = headerValue.Replace("\r\n", string.Empty);
builder.Append(separator);
builder.Append(trimmedValue);
separator = ",";
}
sb.Append(builder);
sb.Append("\n");
}
return sb.ToString();
}
public static string GetCanonicalizedResource(Uri address, string accountName)
{
StringBuilder str = new StringBuilder();
StringBuilder builder = new StringBuilder("/");
builder.Append(accountName);
builder.Append(address.AbsolutePath);
str.Append(builder);
Dictionary<string, HashSet<string>> queryKeyValues = ExtractQueryKeyValues(address);
Dictionary<string, HashSet<string>> dictionary = GetCommaSeparatedList(queryKeyValues);
foreach (KeyValuePair<string, HashSet<string>> pair in dictionary.OrderBy(p => p.Key))
{
StringBuilder stringBuilder = new StringBuilder(string.Empty);
stringBuilder.Append(pair.Key + ":");
string commaList = string.Join(",", pair.Value);
stringBuilder.Append(commaList);
str.Append("\n");
str.Append(stringBuilder);
}
return str.ToString();
}
public static List<string> GetHeaderValues(HttpRequestHeaders headers, string headerName)
{
List<string> list = new List<string>();
IEnumerable<string> values;
headers.TryGetValues(headerName, out values);
if (values != null)
{
list.Add((values.FirstOrDefault() ?? string.Empty).TrimStart(null));
}
return list;
}
private static bool IsWithinRetryRange(HttpStatusCode statusCode)
{
// Retry on http client and server error codes (4xx - 5xx) as well as redirect
var rawStatus = (int)statusCode;
if (rawStatus == 302)
return true;
else if (rawStatus >= 400 && rawStatus <= 599)
return true;
else
return false;
}
public static async Task<HttpResponseMessage> RequestWithRetry(TaskLoggingHelper loggingHelper, HttpClient client,
Func<HttpRequestMessage> createRequest, Func<HttpResponseMessage, bool> validationCallback = null, int retryCount = 5,
int retryDelaySeconds = 5)
{
if (loggingHelper == null)
throw new ArgumentNullException(nameof(loggingHelper));
if (client == null)
throw new ArgumentNullException(nameof(client));
if (createRequest == null)
throw new ArgumentNullException(nameof(createRequest));
if (retryCount < 1)
throw new ArgumentException(nameof(retryCount));
if (retryDelaySeconds < 1)
throw new ArgumentException(nameof(retryDelaySeconds));
int retries = 0;
HttpResponseMessage response = null;
// add a bit of randomness to the retry delay
var rng = new Random();
while (retries < retryCount)
{
if (retries > 0)
{
if (response != null)
{
response.Dispose();
response = null;
}
int delay = retryDelaySeconds * retries * rng.Next(1, 5);
loggingHelper.LogMessage(MessageImportance.Low, "Waiting {0} seconds before retry", delay);
await System.Threading.Tasks.Task.Delay(delay * 1000);
}
try
{
using (var request = createRequest())
response = await client.SendAsync(request);
}
catch (Exception e)
{
loggingHelper.LogWarningFromException(e, true);
// if this is the final iteration let the exception bubble up
if (retries + 1 == retryCount)
throw;
}
// response can be null if we fail to send the request
if (response != null)
{
if (validationCallback == null)
{
// check if the response code is within the range of failures
if (IsWithinRetryRange(response.StatusCode))
{
loggingHelper.LogWarning("Request failed with status code {0}", response.StatusCode);
}
else
{
loggingHelper.LogMessage(MessageImportance.Low, "Response completed with status code {0}", response.StatusCode);
return response;
}
}
else
{
bool isSuccess = validationCallback(response);
if (!isSuccess)
{
loggingHelper.LogMessage("Validation callback returned retry for status code {0}", response.StatusCode);
}
else
{
loggingHelper.LogMessage("Validation callback returned success for status code {0}", response.StatusCode);
return response;
}
}
}
++retries;
}
// retry count exceeded
loggingHelper.LogWarning("Retry count {0} exceeded", retryCount);
// set some default values in case response is null
var statusCode = "None";
var contentStr = "Null";
if (response != null)
{
statusCode = response.StatusCode.ToString();
contentStr = await response.Content.ReadAsStringAsync();
response.Dispose();
}
throw new HttpRequestException(string.Format("Request failed with status {0} response {1}", statusCode, contentStr));
}
private static string ConstructServiceStringToSign(
string signedPermissions,
string signedVersion,
string signedExpiry,
string canonicalizedResource,
string signedIdentifier,
string signedStart,
string signedIP = "",
string signedProtocol = "",
string rscc = "",
string rscd = "",
string rsce = "",
string rscl = "",
string rsct = "")
{
// constructing string to sign based on spec in https://msdn.microsoft.com/en-us/library/azure/dn140255.aspx
var stringToSign = string.Join(
"\n",
signedPermissions,
signedStart,
signedExpiry,
canonicalizedResource,
signedIdentifier,
signedIP,
signedProtocol,
signedVersion,
rscc,
rscd,
rsce,
rscl,
rsct);
return stringToSign;
}
private static Dictionary<string, HashSet<string>> ExtractQueryKeyValues(Uri address)
{
Dictionary<string, HashSet<string>> values = new Dictionary<string, HashSet<string>>();
//Decode this to allow the regex to pull out the correct groups for signing
address = new Uri(WebUtility.UrlDecode(address.ToString()));
Regex newreg = new Regex(@"\?(\w+)\=([\w|\=]+)|\&(\w+)\=([\w|\=]+)");
MatchCollection matches = newreg.Matches(address.Query);
foreach (Match match in matches)
{
string key, value;
if (!string.IsNullOrEmpty(match.Groups[1].Value))
{
key = match.Groups[1].Value;
value = match.Groups[2].Value;
}
else
{
key = match.Groups[3].Value;
value = match.Groups[4].Value;
}
HashSet<string> setOfValues;
if (values.TryGetValue(key, out setOfValues))
{
setOfValues.Add(value);
}
else
{
HashSet<string> newSet = new HashSet<string> { value };
values.Add(key, newSet);
}
}
return values;
}
private static Dictionary<string, HashSet<string>> GetCommaSeparatedList(
Dictionary<string, HashSet<string>> queryKeyValues)
{
Dictionary<string, HashSet<string>> dictionary = new Dictionary<string, HashSet<string>>();
foreach (string queryKeys in queryKeyValues.Keys)
{
HashSet<string> setOfValues;
queryKeyValues.TryGetValue(queryKeys, out setOfValues);
List<string> list = new List<string>();
list.AddRange(setOfValues);
list.Sort();
string commaSeparatedValues = string.Join(",", list);
string key = queryKeys.ToLowerInvariant();
HashSet<string> setOfValues2;
if (dictionary.TryGetValue(key, out setOfValues2))
{
setOfValues2.Add(commaSeparatedValues);
}
else
{
HashSet<string> newSet = new HashSet<string> { commaSeparatedValues };
dictionary.Add(key, newSet);
}
}
return dictionary;
}
}
}

View file

@ -0,0 +1,167 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.Build.Framework;
using Task = Microsoft.Build.Utilities.Task;
namespace Microsoft.DotNet.Build.CloudTestTasks
{
public sealed class CreateAzureContainer : Task
{
/// <summary>
/// The Azure account key used when creating the connection string.
/// </summary>
[Required]
public string AccountKey { get; set; }
/// <summary>
/// The Azure account name used when creating the connection string.
/// </summary>
[Required]
public string AccountName { get; set; }
/// <summary>
/// The name of the container to create. The specified name must be in the correct format, see the
/// following page for more info. https://msdn.microsoft.com/en-us/library/azure/dd135715.aspx
/// </summary>
[Required]
public string ContainerName { get; set; }
/// <summary>
/// When false, if the specified container already exists get a reference to it.
/// When true, if the specified container already exists the task will fail.
/// </summary>
public bool FailIfExists { get; set; }
/// <summary>
/// The read-only SAS token created when ReadOnlyTokenDaysValid is greater than zero.
/// </summary>
[Output]
public string ReadOnlyToken { get; set; }
/// <summary>
/// The number of days for which the read-only token should be valid.
/// </summary>
public int ReadOnlyTokenDaysValid { get; set; }
/// <summary>
/// The URI of the created container.
/// </summary>
[Output]
public string StorageUri { get; set; }
/// <summary>
/// The write-only SAS token create when WriteOnlyTokenDaysValid is greater than zero.
/// </summary>
[Output]
public string WriteOnlyToken { get; set; }
/// <summary>
/// The number of days for which the write-only token should be valid.
/// </summary>
public int WriteOnlyTokenDaysValid { get; set; }
public override bool Execute()
{
return ExecuteAsync().GetAwaiter().GetResult();
}
public async Task<bool> ExecuteAsync()
{
Log.LogMessage(
MessageImportance.High,
"Creating container named '{0}' in storage account {1}.",
ContainerName,
AccountName);
string url = string.Format(
"https://{0}.blob.core.windows.net/{1}?restype=container",
AccountName,
ContainerName);
StorageUri = string.Format(
"https://{0}.blob.core.windows.net/{1}/",
AccountName,
ContainerName);
Log.LogMessage(MessageImportance.Low, "Sending request to create Container");
using (HttpClient client = new HttpClient())
{
Func<HttpRequestMessage> createRequest = () =>
{
DateTime dt = DateTime.UtcNow;
var req = new HttpRequestMessage(HttpMethod.Put, url);
req.Headers.Add(AzureHelper.DateHeaderString, dt.ToString("R", CultureInfo.InvariantCulture));
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
req.Headers.Add(AzureHelper.AuthorizationHeaderString, AzureHelper.AuthorizationHeader(
AccountName,
AccountKey,
"PUT",
dt,
req));
byte[] bytestoWrite = new byte[0];
int bytesToWriteLength = 0;
Stream postStream = new MemoryStream();
postStream.Write(bytestoWrite, 0, bytesToWriteLength);
req.Content = new StreamContent(postStream);
return req;
};
Func<HttpResponseMessage, bool> validate = (HttpResponseMessage response) =>
{
// the Conflict status (409) indicates that the container already exists, so
// if FailIfExists is set to false and we get a 409 don't fail the task.
return response.IsSuccessStatusCode || (!FailIfExists && response.StatusCode == HttpStatusCode.Conflict);
};
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(Log, client, createRequest, validate))
{
try
{
Log.LogMessage(
MessageImportance.Low,
"Received response to create Container {0}: Status Code: {1} {2}",
ContainerName, response.StatusCode, response.Content.ToString());
// specifying zero is valid, it means "I don't want a token"
if (ReadOnlyTokenDaysValid > 0)
{
ReadOnlyToken = AzureHelper.CreateContainerSasToken(
AccountName,
ContainerName,
AccountKey,
AzureHelper.SasAccessType.Read,
ReadOnlyTokenDaysValid);
}
// specifying zero is valid, it means "I don't want a token"
if (WriteOnlyTokenDaysValid > 0)
{
WriteOnlyToken = AzureHelper.CreateContainerSasToken(
AccountName,
ContainerName,
AccountKey,
AzureHelper.SasAccessType.Write,
WriteOnlyTokenDaysValid);
}
}
catch (Exception e)
{
Log.LogErrorFromException(e, true);
return false;
}
}
}
return true;
}
}
}

View file

@ -0,0 +1,183 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.DotNet.Build.CloudTestTasks
{
public class UploadClient
{
private TaskLoggingHelper log;
public UploadClient(TaskLoggingHelper loggingHelper)
{
log = loggingHelper;
}
public string EncodeBlockIds(int numberOfBlocks, int lengthOfId)
{
string numberOfBlocksString = numberOfBlocks.ToString("D" + lengthOfId);
if (Encoding.UTF8.GetByteCount(numberOfBlocksString) <= 64)
{
byte[] bytes = Encoding.UTF8.GetBytes(numberOfBlocksString);
return Convert.ToBase64String(bytes);
}
else
{
throw new Exception("Task failed - Could not encode block id.");
}
}
public async Task UploadBlockBlobAsync(
CancellationToken ct,
string AccountName,
string AccountKey,
string ContainerName,
string filePath,
string destinationBlob)
{
string resourceUrl = string.Format("https://{0}.blob.core.windows.net/{1}", AccountName, ContainerName);
string fileName = destinationBlob;
fileName = fileName.Replace("\\", "/");
string blobUploadUrl = resourceUrl + "/" + fileName;
int size = (int)new FileInfo(filePath).Length;
int blockSize = 4 * 1024 * 1024; //4MB max size of a block blob
int bytesLeft = size;
List<string> blockIds = new List<string>();
int numberOfBlocks = (size / blockSize) + 1;
int countForId = 0;
using (FileStream fileStreamTofilePath = new FileStream(filePath, FileMode.Open))
{
int offset = 0;
while (bytesLeft > 0)
{
int nextBytesToRead = (bytesLeft < blockSize) ? bytesLeft : blockSize;
byte[] fileBytes = new byte[blockSize];
int read = fileStreamTofilePath.Read(fileBytes, 0, nextBytesToRead);
if (nextBytesToRead != read)
{
throw new Exception(string.Format(
"Number of bytes read ({0}) from file {1} isn't equal to the number of bytes expected ({2}) .",
read, fileName, nextBytesToRead));
}
string blockId = EncodeBlockIds(countForId, numberOfBlocks.ToString().Length);
blockIds.Add(blockId);
string blockUploadUrl = blobUploadUrl + "?comp=block&blockid=" + WebUtility.UrlEncode(blockId);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Clear();
Func<HttpRequestMessage> createRequest = () =>
{
DateTime dt = DateTime.UtcNow;
var req = new HttpRequestMessage(HttpMethod.Put, blockUploadUrl);
req.Headers.Add(
AzureHelper.DateHeaderString,
dt.ToString("R", CultureInfo.InvariantCulture));
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
req.Headers.Add(
AzureHelper.AuthorizationHeaderString,
AzureHelper.AuthorizationHeader(
AccountName,
AccountKey,
"PUT",
dt,
req,
string.Empty,
string.Empty,
nextBytesToRead.ToString(),
string.Empty));
Stream postStream = new MemoryStream();
postStream.Write(fileBytes, 0, nextBytesToRead);
postStream.Seek(0, SeekOrigin.Begin);
req.Content = new StreamContent(postStream);
return req;
};
log.LogMessage(MessageImportance.Low, "Sending request to upload part {0} of file {1}", countForId, fileName);
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(log, client, createRequest))
{
log.LogMessage(
MessageImportance.Low,
"Received response to upload part {0} of file {1}: Status Code:{2} Status Desc: {3}",
countForId,
fileName,
response.StatusCode,
await response.Content.ReadAsStringAsync());
}
}
offset += read;
bytesLeft -= nextBytesToRead;
countForId += 1;
}
}
string blockListUploadUrl = blobUploadUrl + "?comp=blocklist";
using (HttpClient client = new HttpClient())
{
Func<HttpRequestMessage> createRequest = () =>
{
DateTime dt1 = DateTime.UtcNow;
var req = new HttpRequestMessage(HttpMethod.Put, blockListUploadUrl);
req.Headers.Add(AzureHelper.DateHeaderString, dt1.ToString("R", CultureInfo.InvariantCulture));
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
var body = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?><BlockList>");
foreach (object item in blockIds)
body.AppendFormat("<Latest>{0}</Latest>", item);
body.Append("</BlockList>");
byte[] bodyData = Encoding.UTF8.GetBytes(body.ToString());
req.Headers.Add(
AzureHelper.AuthorizationHeaderString,
AzureHelper.AuthorizationHeader(
AccountName,
AccountKey,
"PUT",
dt1,
req,
string.Empty,
string.Empty,
bodyData.Length.ToString(),
""));
Stream postStream = new MemoryStream();
postStream.Write(bodyData, 0, bodyData.Length);
postStream.Seek(0, SeekOrigin.Begin);
req.Content = new StreamContent(postStream);
return req;
};
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(log, client, createRequest))
{
log.LogMessage(
MessageImportance.Low,
"Received response to combine block list for file {0}: Status Code:{1} Status Desc: {2}",
fileName,
response.StatusCode,
await response.Content.ReadAsStringAsync());
}
}
}
}
}

View file

@ -0,0 +1,186 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Linq;
using System.Net.Http;
using Microsoft.Build.Framework;
using Task = Microsoft.Build.Utilities.Task;
using ThreadingTask = System.Threading.Tasks.Task;
namespace Microsoft.DotNet.Build.CloudTestTasks
{
public class UploadToAzure : Task, ICancelableTask
{
private static readonly CancellationTokenSource TokenSource = new CancellationTokenSource();
private static readonly CancellationToken CancellationToken = TokenSource.Token;
/// <summary>
/// The Azure account key used when creating the connection string.
/// </summary>
[Required]
public string AccountKey { get; set; }
/// <summary>
/// The Azure account name used when creating the connection string.
/// </summary>
[Required]
public string AccountName { get; set; }
/// <summary>
/// The name of the container to access. The specified name must be in the correct format, see the
/// following page for more info. https://msdn.microsoft.com/en-us/library/azure/dd135715.aspx
/// </summary>
[Required]
public string ContainerName { get; set; }
/// <summary>
/// An item group of files to upload. Each item must have metadata RelativeBlobPath
/// that specifies the path relative to ContainerName where the item will be uploaded.
/// </summary>
[Required]
public ITaskItem[] Items { get; set; }
/// <summary>
/// Indicates if the destination blob should be overwritten if it already exists. The default if false.
/// </summary>
public bool Overwrite { get; set; } = false;
/// <summary>
/// Specifies the maximum number of clients to concurrently upload blobs to azure
/// </summary>
public int MaxClients { get; set; } = 8;
public void Cancel()
{
TokenSource.Cancel();
}
public override bool Execute()
{
return ExecuteAsync(CancellationToken).GetAwaiter().GetResult();
}
public async Task<bool> ExecuteAsync(CancellationToken ct)
{
Log.LogMessage(
MessageImportance.High,
"Begin uploading blobs to Azure account {0} in container {1}.",
AccountName,
ContainerName);
if (Items.Length == 0)
{
Log.LogError("No items were provided for upload.");
return false;
}
// first check what blobs are present
string checkListUrl = string.Format(
"https://{0}.blob.core.windows.net/{1}?restype=container&comp=list",
AccountName,
ContainerName);
HashSet<string> blobsPresent = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
try
{
using (HttpClient client = new HttpClient())
{
Func<HttpRequestMessage> createRequest = () =>
{
DateTime dt = DateTime.UtcNow;
var req = new HttpRequestMessage(HttpMethod.Get, checkListUrl);
req.Headers.Add(AzureHelper.DateHeaderString, dt.ToString("R", CultureInfo.InvariantCulture));
req.Headers.Add(AzureHelper.VersionHeaderString, AzureHelper.StorageApiVersion);
req.Headers.Add(AzureHelper.AuthorizationHeaderString, AzureHelper.AuthorizationHeader(
AccountName,
AccountKey,
"GET",
dt,
req));
return req;
};
Log.LogMessage(MessageImportance.Low, "Sending request to check whether Container blobs exist");
using (HttpResponseMessage response = await AzureHelper.RequestWithRetry(Log, client, createRequest))
{
var doc = new XmlDocument();
doc.LoadXml(await response.Content.ReadAsStringAsync());
XmlNodeList nodes = doc.DocumentElement.GetElementsByTagName("Blob");
foreach (XmlNode node in nodes)
{
blobsPresent.Add(node["Name"].InnerText);
}
Log.LogMessage(MessageImportance.Low, "Received response to check whether Container blobs exist");
}
}
using (var clientThrottle = new SemaphoreSlim(this.MaxClients, this.MaxClients))
{
await ThreadingTask.WhenAll(Items.Select(item => UploadAsync(ct, item, blobsPresent, clientThrottle)));
}
Log.LogMessage(MessageImportance.High, "Upload to Azure is complete, a total of {0} items were uploaded.", Items.Length);
return true;
}
catch (Exception e)
{
Log.LogErrorFromException(e, true);
return false;
}
}
private async ThreadingTask UploadAsync(CancellationToken ct, ITaskItem item, HashSet<string> blobsPresent, SemaphoreSlim clientThrottle)
{
if (ct.IsCancellationRequested)
{
Log.LogError("Task UploadToAzure cancelled");
ct.ThrowIfCancellationRequested();
}
string relativeBlobPath = item.GetMetadata("RelativeBlobPath");
if (string.IsNullOrEmpty(relativeBlobPath))
throw new Exception(string.Format("Metadata 'RelativeBlobPath' is missing for item '{0}'.", item.ItemSpec));
if (!File.Exists(item.ItemSpec))
throw new Exception(string.Format("The file '{0}' does not exist.", item.ItemSpec));
if (!Overwrite && blobsPresent.Contains(relativeBlobPath))
throw new Exception(string.Format("The blob '{0}' already exists.", relativeBlobPath));
await clientThrottle.WaitAsync();
try
{
Log.LogMessage("Uploading {0} to {1}.", item.ItemSpec, ContainerName);
UploadClient uploadClient = new UploadClient(Log);
await
uploadClient.UploadBlockBlobAsync(
ct,
AccountName,
AccountKey,
ContainerName,
item.ItemSpec,
relativeBlobPath);
}
finally
{
clientThrottle.Release();
}
}
}
}

View file

@ -0,0 +1,131 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.IO;
using System.IO.Compression;
using System.Text.RegularExpressions;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.DotNet.Build.Tasks
{
public sealed class ZipFileCreateFromDirectory : Task
{
/// <summary>
/// The path to the directory to be archived.
/// </summary>
[Required]
public string SourceDirectory { get; set; }
/// <summary>
/// The path of the archive to be created.
/// </summary>
[Required]
public string DestinationArchive { get; set; }
/// <summary>
/// Indicates if the destination archive should be overwritten if it already exists.
/// </summary>
public bool OverwriteDestination { get; set; }
/// <summary>
/// If zipping an entire folder without exclusion patterns, whether to include the folder in the archive.
/// </summary>
public bool IncludeBaseDirectory { get; set; }
/// <summary>
/// An item group of regular expressions for content to exclude from the archive.
/// </summary>
public ITaskItem[] ExcludePatterns { get; set; }
public override bool Execute()
{
try
{
if (File.Exists(DestinationArchive))
{
if (OverwriteDestination == true)
{
Log.LogMessage(MessageImportance.Low, "{0} already existed, deleting before zipping...", DestinationArchive);
File.Delete(DestinationArchive);
}
else
{
Log.LogWarning("'{0}' already exists. Did you forget to set '{1}' to true?", DestinationArchive, nameof(OverwriteDestination));
}
}
Log.LogMessage(MessageImportance.High, "Compressing {0} into {1}...", SourceDirectory, DestinationArchive);
if (!Directory.Exists(Path.GetDirectoryName(DestinationArchive)))
Directory.CreateDirectory(Path.GetDirectoryName(DestinationArchive));
if (ExcludePatterns == null)
{
ZipFile.CreateFromDirectory(SourceDirectory, DestinationArchive, CompressionLevel.Optimal, IncludeBaseDirectory);
}
else
{
// convert to regular expressions
Regex[] regexes = new Regex[ExcludePatterns.Length];
for (int i = 0; i < ExcludePatterns.Length; ++i)
regexes[i] = new Regex(ExcludePatterns[i].ItemSpec, RegexOptions.IgnoreCase);
using (FileStream writer = new FileStream(DestinationArchive, FileMode.CreateNew))
{
using (ZipArchive zipFile = new ZipArchive(writer, ZipArchiveMode.Create))
{
var files = Directory.GetFiles(SourceDirectory, "*", SearchOption.AllDirectories);
foreach (var file in files)
{
// look for a match
bool foundMatch = false;
foreach (var regex in regexes)
{
if (regex.IsMatch(file))
{
foundMatch = true;
break;
}
}
if (foundMatch)
{
Log.LogMessage(MessageImportance.Low, "Excluding {0} from archive.", file);
continue;
}
var relativePath = MakeRelativePath(SourceDirectory, file);
zipFile.CreateEntryFromFile(file, relativePath, CompressionLevel.Optimal);
}
}
}
}
}
catch (Exception e)
{
// We have 2 log calls because we want a nice error message but we also want to capture the callstack in the log.
Log.LogError("An exception has occured while trying to compress '{0}' into '{1}'.", SourceDirectory, DestinationArchive);
Log.LogMessage(MessageImportance.Low, e.ToString());
return false;
}
return true;
}
private string MakeRelativePath(string root, string subdirectory)
{
if (!subdirectory.StartsWith(root))
throw new Exception(string.Format("'{0}' is not a subdirectory of '{1}'.", subdirectory, root));
// returned string should not start with a directory separator
int chop = root.Length;
if (subdirectory[chop] == Path.DirectorySeparatorChar)
++chop;
return subdirectory.Substring(chop);
}
}
}

View file

@ -0,0 +1,65 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using System;
using System.IO;
using System.IO.Compression;
namespace Microsoft.DotNet.Build.Tasks
{
public sealed class ZipFileExtractToDirectory : Task
{
/// <summary>
/// The path to the directory to be archived.
/// </summary>
[Required]
public string SourceArchive { get; set; }
/// <summary>
/// The path of the archive to be created.
/// </summary>
[Required]
public string DestinationDirectory { get; set; }
/// <summary>
/// Indicates if the destination archive should be overwritten if it already exists.
/// </summary>
public bool OverwriteDestination { get; set; }
public override bool Execute()
{
try
{
if (Directory.Exists(DestinationDirectory))
{
if (OverwriteDestination == true)
{
Log.LogMessage(MessageImportance.Low, "'{0}' already exists, trying to delete before unzipping...", DestinationDirectory);
Directory.Delete(DestinationDirectory, recursive: true);
}
else
{
Log.LogWarning("'{0}' already exists. Did you forget to set '{1}' to true?", DestinationDirectory, nameof(OverwriteDestination));
}
}
Log.LogMessage(MessageImportance.High, "Decompressing '{0}' into '{1}'...", SourceArchive, DestinationDirectory);
if (!Directory.Exists(Path.GetDirectoryName(DestinationDirectory)))
Directory.CreateDirectory(Path.GetDirectoryName(DestinationDirectory));
ZipFile.ExtractToDirectory(SourceArchive, DestinationDirectory);
}
catch (Exception e)
{
// We have 2 log calls because we want a nice error message but we also want to capture the callstack in the log.
Log.LogError("An exception has occured while trying to decompress '{0}' into '{1}'.", SourceArchive, DestinationDirectory);
Log.LogMessage(MessageImportance.Low, e.ToString());
return false;
}
return true;
}
}
}

View file

@ -21,8 +21,8 @@
"System.Xml.XmlSerializer": "4.0.11",
"WindowsAzure.Storage": "6.2.2-preview",
"NuGet.CommandLine.XPlat": "3.5.0-rc1-1697",
"Microsoft.Build.Framework": "0.1.0-preview-00024-160610",
"Microsoft.Build.Utilities.Core": "0.1.0-preview-00024-160610"
"Microsoft.Build.Framework": "0.1.0-preview-00029-160805",
"Microsoft.Build.Utilities.Core": "0.1.0-preview-00029-160805"
},
"frameworks": {
"netcoreapp1.0": {

View file

@ -58,7 +58,8 @@ param(
[switch]$DebugSymbols, # TODO: Switch does not work yet. Symbols zip is not being uploaded yet.
[switch]$DryRun,
[switch]$NoPath,
[string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet"
[string]$AzureFeed="https://dotnetcli.azureedge.net/dotnet",
[string]$UncachedFeed="https://dotnetcli.blob.core.windows.net/dotnet"
)
Set-StrictMode -Version Latest
@ -120,10 +121,10 @@ function Get-Latest-Version-Info([string]$AzureFeed, [string]$AzureChannel, [str
$VersionFileUrl = $null
if ($SharedRuntime) {
$VersionFileUrl = "$AzureFeed/$AzureChannel/dnvm/latest.sharedfx.win.$CLIArchitecture.version"
$VersionFileUrl = "$UncachedFeed/$AzureChannel/dnvm/latest.sharedfx.win.$CLIArchitecture.version"
}
else {
$VersionFileUrl = "$AzureFeed/Sdk/$AzureChannel/latest.version"
$VersionFileUrl = "$UncachedFeed/Sdk/$AzureChannel/latest.version"
}
$Response = Invoke-WebRequest -UseBasicParsing $VersionFileUrl

View file

@ -286,9 +286,9 @@ get_latest_version_info() {
local version_file_url=null
if [ "$shared_runtime" = true ]; then
version_file_url="$azure_feed/$azure_channel/dnvm/latest.sharedfx.$osname.$normalized_architecture.version"
version_file_url="$uncached_feed/$azure_channel/dnvm/latest.sharedfx.$osname.$normalized_architecture.version"
else
version_file_url="$azure_feed/Sdk/$azure_channel/latest.version"
version_file_url="$uncached_feed/Sdk/$azure_channel/latest.version"
fi
say_verbose "get_latest_version_info: latest url: $version_file_url"
@ -557,6 +557,7 @@ debug_symbols=false
dry_run=false
no_path=false
azure_feed="https://dotnetcli.azureedge.net/dotnet"
uncached_feed="https://dotnetcli.blob.core.windows.net/dotnet"
verbose=false
shared_runtime=false

View file

@ -27,7 +27,7 @@
},
"publishOptions": {
"include": [
"14.1/**",
"15.0/**",
"*.props",
"*.targets"
]