зеркало из https://github.com/microsoft/BuildXL.git
Merged PR 662122: Revert "Merged PR 661589: Reapply: nuget resolver schedules real pips"
Revert "Merged PR 661589: Reapply: nuget resolver schedules real pips"
This reverts commit 53d8270164
.
This change is still causing some breaks in downstream validation
This commit is contained in:
Родитель
06b1f1c39c
Коммит
4e9cff438c
|
@ -179,9 +179,9 @@ export function evaluate(args: Arguments): Result {
|
|||
],
|
||||
allowedSurvivingChildProcessNames: [
|
||||
"mspdbsrv.exe",
|
||||
"VCTIP.exe",
|
||||
"conhost.exe",
|
||||
],
|
||||
unsafe: { childProcessesToBreakawayFromSandbox: [a`vctip.exe`] }
|
||||
]
|
||||
});
|
||||
|
||||
let midlOutput = <Midl.Result>{
|
||||
|
|
|
@ -27,16 +27,13 @@ Each time a new nuget version is updated we must make sure that any configuratio
|
|||
<solution>
|
||||
<clear />
|
||||
</solution>
|
||||
<packageSourceCredentials>
|
||||
<clear />
|
||||
</packageSourceCredentials>
|
||||
<apikeys>
|
||||
<clear />
|
||||
</apikeys>
|
||||
<activePackageSource>
|
||||
<clear />
|
||||
</activePackageSource>
|
||||
<fallbackPackageFolders>
|
||||
<clear />
|
||||
</fallbackPackageFolders>
|
||||
<packageSourceMapping>
|
||||
<clear />
|
||||
</packageSourceMapping>
|
||||
</configuration>
|
|
@ -14,8 +14,7 @@ export const tool : Transformer.ToolDefinition = {
|
|||
exe: Nuget.Contents.all.getFile(r`tools/NuGet.exe`),
|
||||
description: "NuGet pack",
|
||||
untrackedDirectoryScopes: [
|
||||
d`${Context.getMount("ProgramData").path}/Nuget`,
|
||||
...addIfLazy(Context.isWindowsOS(), () => [d`${Context.getMount("ProgramFilesX86").path}/Nuget`]),
|
||||
d`${Context.getMount("ProgramData").path}/Nuget`
|
||||
],
|
||||
dependsOnWindowsDirectories: true,
|
||||
dependsOnAppDataDirectory: true,
|
||||
|
|
|
@ -839,11 +839,7 @@ interface UntrackingSettings {
|
|||
}
|
||||
|
||||
interface NuGetConfiguration extends ToolConfiguration {
|
||||
/**
|
||||
* The download timeout, in minutes, for each NuGet download pip. Defaults to 20m.
|
||||
* Equivalent to configuring timeoutInMilliseconds for the corresponding Transformers.ToolDefinition
|
||||
*/
|
||||
downloadTimeoutMin?: number
|
||||
credentialProviders?: ToolConfiguration[];
|
||||
}
|
||||
|
||||
interface ScriptResolverDefaults {
|
||||
|
|
|
@ -184,9 +184,9 @@ function runTest(args : TestRunArguments) : File[] {
|
|||
testAssembly: args.testDeployment.primaryFile.path,
|
||||
qTestType: Qtest.QTestType.msTest_latest,
|
||||
qTestDirToDeploy: args.testDeployment.contents,
|
||||
qTestAdapterPath: Transformer.sealPartialDirectory({
|
||||
qTestAdapterPath: Transformer.sealDirectory({
|
||||
root: testAdapterPath,
|
||||
files: rootTestAdapterPath.contents.filter(f => f.path.isWithin(testAdapterPath)),
|
||||
files: globR(testAdapterPath, "*")
|
||||
}),
|
||||
qTestDotNetFramework: getQTestDotNetFramework(),
|
||||
qTestPlatform: Qtest.QTestPlatform.x64,
|
||||
|
|
|
@ -23,24 +23,10 @@ namespace BuildXL.Engine
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds all the mounts specified in the given mounts table
|
||||
/// </summary>
|
||||
public void UpdateMountsTable(MountsTable mountsTable)
|
||||
/// <nodoc />
|
||||
public void SetMountsTable(MountsTable mountsTable)
|
||||
{
|
||||
var allMounts = mountsTable.AllMountsSoFar.ToDictionary(mount => mount.Name.ToString(m_pathTable.StringTable), mount => mount);
|
||||
|
||||
if (m_customMountsTable == null)
|
||||
{
|
||||
m_customMountsTable = allMounts;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var mount in allMounts)
|
||||
{
|
||||
m_customMountsTable[mount.Key] = mount.Value;
|
||||
}
|
||||
}
|
||||
m_customMountsTable = mountsTable.AllMountsSoFar.ToDictionary(mount => mount.Name.ToString(m_pathTable.StringTable), mount => mount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -55,7 +41,7 @@ namespace BuildXL.Engine
|
|||
return false;
|
||||
}
|
||||
|
||||
UpdateMountsTable(mountsTable);
|
||||
SetMountsTable(mountsTable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -93,7 +93,6 @@ namespace BuildXL.Engine
|
|||
private readonly MountsTable m_mountsTable;
|
||||
|
||||
private readonly LoggingContext m_loggingContext;
|
||||
private readonly DirectoryTranslator m_directoryTranslator;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="FrontEndEngineImplementation"/>.
|
||||
|
@ -131,7 +130,6 @@ namespace BuildXL.Engine
|
|||
m_snapshotCollector = snapshotCollector;
|
||||
GetTimerUpdatePeriod = timerUpdatePeriod;
|
||||
Layout = configuration.Layout;
|
||||
m_directoryTranslator = directoryTranslator;
|
||||
|
||||
if (ShouldUseSpecCache(configuration))
|
||||
{
|
||||
|
@ -186,12 +184,6 @@ namespace BuildXL.Engine
|
|||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AbsolutePath Translate(AbsolutePath path)
|
||||
{
|
||||
return m_directoryTranslator?.Translate(path, PathTable) ?? path;
|
||||
}
|
||||
|
||||
private bool ShouldUseSpecCache(IConfiguration configuration)
|
||||
{
|
||||
var sourceDirectory = configuration.Layout.SourceDirectory.ToString(PathTable);
|
||||
|
|
|
@ -197,7 +197,7 @@ function execute(args: Transformer.ExecuteArguments): Transformer.ExecuteResult
|
|||
}
|
||||
|
||||
|
||||
protected void AddSdk(string relativeSdkLocation)
|
||||
protected void AddSdk(string sdkLocation)
|
||||
{
|
||||
Configuration.Resolvers.Add(
|
||||
new SourceResolverSettings
|
||||
|
@ -206,7 +206,7 @@ function execute(args: Transformer.ExecuteArguments): Transformer.ExecuteResult
|
|||
Modules = new List<DiscriminatingUnion<AbsolutePath, IInlineModuleDefinition>>
|
||||
{
|
||||
new DiscriminatingUnion<AbsolutePath, IInlineModuleDefinition>(
|
||||
AbsolutePath.Create(Context.PathTable, TestDeploymentDir).Combine(Context.PathTable, RelativePath.Create(Context.StringTable, relativeSdkLocation))),
|
||||
AbsolutePath.Create(Context.PathTable, sdkLocation)),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ namespace Test.BuildXL.Engine
|
|||
{
|
||||
private CacheInitializer m_cacheInitializer;
|
||||
|
||||
|
||||
/* The build used for tests in this class generate the following layout
|
||||
*
|
||||
* \HelloWorldFinal.exe.config (source)
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
import * as DetoursServices from "BuildXL.Sandbox.Windows";
|
||||
import * as Xml from "Sdk.Xml";
|
||||
import * as Deployment from "Sdk.Deployment";
|
||||
import {Transformer} from "Sdk.Transformers";
|
||||
|
||||
namespace Engine {
|
||||
|
||||
|
@ -25,34 +23,8 @@ namespace Engine {
|
|||
},
|
||||
];
|
||||
|
||||
// We generate specs as if the compilers package was source files, but point it to the downloaded NuGet
|
||||
const compilerSpecsDir = Context.getNewOutputDirectory("compilers-specs");
|
||||
const microsoftNetCompilerSpec = Transformer.writeAllText(p`${compilerSpecsDir}/module.config.bm`,
|
||||
"module({name: 'Microsoft.Net.Compilers', version: '4.0.1', nameResolutionSemantics: NameResolutionSemantics.implicitProjectReferences});");
|
||||
const specFile = Transformer.writeAllLines(p`${compilerSpecsDir}/package.dsc`, [
|
||||
"import {Transformer} from 'Sdk.Transformers';",
|
||||
"namespace Contents {",
|
||||
"export declare const qualifier: {};",
|
||||
"@@public export const all: StaticDirectory = Transformer.sealPartialDirectory(d`package`, globR(d`package`, '*'));",
|
||||
"}"
|
||||
]);
|
||||
|
||||
// Deploy the compilers package plus the specs that refer to it
|
||||
const deployable : Deployment.Definition = {
|
||||
contents: [
|
||||
{
|
||||
subfolder: a`compilers`,
|
||||
contents: [
|
||||
{
|
||||
subfolder: a`package`,
|
||||
contents: [importFrom('Microsoft.Net.Compilers').Contents.all]
|
||||
},
|
||||
specFile,
|
||||
microsoftNetCompilerSpec
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
// Update the value of this variable if you change the version of Microsoft.Net.Compilers in config.dsc.
|
||||
const microsoftNetCompilerSpec = f`${Context.getMount("FrontEnd").path}/Nuget/specs/Microsoft.Net.Compilers/4.0.1/module.config.bm`;
|
||||
|
||||
@@public
|
||||
export const categoriesToRunInParallel = [
|
||||
|
@ -75,11 +47,12 @@ namespace Engine {
|
|||
},
|
||||
parallelGroups: categoriesToRunInParallel,
|
||||
testRunData: {
|
||||
MicrosoftNetCompilersSdkLocation: "compilers/module.config.bm",
|
||||
MicrosoftNetCompilersSdkLocation: microsoftNetCompilerSpec,
|
||||
},
|
||||
tools: {
|
||||
exec: {
|
||||
dependencies: [
|
||||
microsoftNetCompilerSpec,
|
||||
importFrom("Microsoft.Net.Compilers").Contents.all,
|
||||
importFrom("Microsoft.NETCore.Compilers").Contents.all,
|
||||
]
|
||||
|
@ -114,7 +87,6 @@ namespace Engine {
|
|||
],
|
||||
runtimeContent: [
|
||||
...libsUsedForTesting,
|
||||
deployable
|
||||
],
|
||||
});
|
||||
}
|
||||
|
|
|
@ -882,7 +882,6 @@ namespace Test.BuildXL.Scheduler
|
|||
bool preservePathSetCasing = source.Vary(p => p.PreservePathSetCasing);
|
||||
bool writingToStandardErrorFailsExecution = source.Vary(p => p.WritingToStandardErrorFailsExecution);
|
||||
bool disableFullReparsePointResolving = source.Vary(p => p.DisableFullReparsePointResolving);
|
||||
bool bypassFingerprintSalt = source.Vary(p => p.BypassFingerprintSalt);
|
||||
|
||||
Process.Options options = Process.Options.None;
|
||||
if (hasUntrackedChildProcesses)
|
||||
|
@ -941,11 +940,6 @@ namespace Test.BuildXL.Scheduler
|
|||
options |= Process.Options.DisableFullReparsePointResolving;
|
||||
}
|
||||
|
||||
if (bypassFingerprintSalt)
|
||||
{
|
||||
options |= Process.Options.BypassFingerprintSalt;
|
||||
}
|
||||
|
||||
return new Process(
|
||||
executable: executable,
|
||||
workingDirectory: workingDirectory,
|
||||
|
|
|
@ -52,7 +52,6 @@ namespace BuildXL.FrontEnd.Core
|
|||
string weakPackageFingerprint,
|
||||
PackageIdentity package,
|
||||
AbsolutePath packageTargetFolder,
|
||||
AbsolutePath pathToNuspec,
|
||||
Func<Task<Possible<IReadOnlyList<RelativePath>>>> producePackage)
|
||||
{
|
||||
// Check if we can reuse a package that is layed out on disk already.
|
||||
|
@ -98,7 +97,6 @@ namespace BuildXL.FrontEnd.Core
|
|||
weakPackageFingerprint,
|
||||
package,
|
||||
packageTargetFolder,
|
||||
pathToNuspec,
|
||||
possiblePackageContents.Result),
|
||||
justification: "Okay to ignore putting in cache failure, will happen next time"
|
||||
);
|
||||
|
@ -356,7 +354,7 @@ namespace BuildXL.FrontEnd.Core
|
|||
|
||||
// Saving package's hash file on disk.
|
||||
// But we can do this only when the content is not empty.
|
||||
var packagesContent = packageDescriptor.Contents.Select(k => k.Key).ToList();
|
||||
var packagesContent = packageDescriptor.Contents.Select(k => k.Key).ToList();
|
||||
|
||||
if (packagesContent.Count == 0)
|
||||
{
|
||||
|
@ -365,6 +363,9 @@ namespace BuildXL.FrontEnd.Core
|
|||
return PackageDownloadResult.RecoverableError(package);
|
||||
}
|
||||
|
||||
var newPackageHash = new PackageHashFile(weakPackageFingerprintHash.Hash.ToHex(), weakPackageFingerprint, packagesContent);
|
||||
TryUpdatePackageHashFile(loggingContext, package, packageHashFile, packageHash, newPackageHash);
|
||||
|
||||
m_logger.PackageRestoredFromCache(loggingContext, package.GetFriendlyName());
|
||||
|
||||
if (forcePopulatePackageCache)
|
||||
|
@ -393,20 +394,11 @@ namespace BuildXL.FrontEnd.Core
|
|||
}
|
||||
}
|
||||
|
||||
// The hash file is stored as part of the package, so we have the package layout available there
|
||||
var maybePackageHashFile = PackageHashFile.TryReadFrom(GetPackageHashFile(packageTargetFolder));
|
||||
|
||||
if (!maybePackageHashFile.Succeeded)
|
||||
{
|
||||
m_logger.DownloadPackageFailedDueToCacheError(loggingContext, friendlyName, maybePackageHashFile.Failure.Describe());
|
||||
return PackageDownloadResult.RecoverableError(package);
|
||||
}
|
||||
|
||||
// Step: Return descriptor indicating it was successfully restored from the cache
|
||||
return PackageDownloadResult.FromCache(
|
||||
package,
|
||||
packageTargetFolder,
|
||||
maybePackageHashFile.Result.Content.Select(entry => RelativePath.Create(FrontEndContext.StringTable, entry)).ToList(),
|
||||
packageDescriptor.Contents.Select(c => RelativePath.Create(FrontEndContext.StringTable, c.Key)).ToList(),
|
||||
weakPackageFingerprintHash.Hash.ToHex());
|
||||
}
|
||||
|
||||
|
@ -415,7 +407,6 @@ namespace BuildXL.FrontEnd.Core
|
|||
string weakPackageFingerprint,
|
||||
PackageIdentity package,
|
||||
AbsolutePath packageTargetFolder,
|
||||
AbsolutePath pathToNuspec,
|
||||
IReadOnlyList<RelativePath> packageContent)
|
||||
{
|
||||
var friendlyName = package.GetFriendlyName();
|
||||
|
@ -432,22 +423,11 @@ namespace BuildXL.FrontEnd.Core
|
|||
// Cache was already initialized
|
||||
var cache = await m_nugetCache;
|
||||
|
||||
// Generate the hash file, since that is stored as part of the cache content
|
||||
var weakPackageFingerprintHash = cache.GetDownloadFingerprint(weakPackageFingerprint);
|
||||
// The content should have relative paths
|
||||
var content = packageContents.Select(rp => rp.ToString(PathTable.StringTable)).ToList();
|
||||
var newHash = new PackageHashFile(weakPackageFingerprintHash.Hash.ToHex(), weakPackageFingerprint, content);
|
||||
var packageHashFile = GetPackageHashFile(packageTargetFolder);
|
||||
TryUpdatePackageHashFile(loggingContext, package, packageHashFile, oldHash: null, newHash: newHash);
|
||||
|
||||
// Step: Store the nuspec and the hash file into the content cache
|
||||
// Step: Store all the files into the content cache
|
||||
var stringKeyedHashes = new List<StringKeyedHash>();
|
||||
|
||||
foreach (var absolutePath in new[] { pathToNuspec, AbsolutePath.Create(PathTable, packageHashFile)})
|
||||
foreach (var relativePath in packageContents)
|
||||
{
|
||||
var targetFileLocation = absolutePath.Expand(PathTable);
|
||||
var result = packageTargetFolder.TryGetRelative(PathTable, absolutePath, out var relativePath);
|
||||
Contract.Assert(result);
|
||||
var targetFileLocation = packageTargetFolder.Combine(PathTable, relativePath).Expand(PathTable);
|
||||
|
||||
ContentHash contentHash;
|
||||
try
|
||||
|
@ -473,6 +453,7 @@ namespace BuildXL.FrontEnd.Core
|
|||
}
|
||||
}
|
||||
|
||||
var weakPackageFingerprintHash = cache.GetDownloadFingerprint(weakPackageFingerprint);
|
||||
// Step: Create a descriptor and store that in the fingerprint store under the weak fingerprint.
|
||||
var cacheDescriptor = PackageDownloadDescriptor.Create(
|
||||
friendlyName,
|
||||
|
@ -490,6 +471,11 @@ namespace BuildXL.FrontEnd.Core
|
|||
|
||||
m_logger.PackageNotFoundInCacheAndDownloaded(loggingContext, package.Id, package.Version, weakPackageFingerprintHash.Hash.ToHex(), weakPackageFingerprint);
|
||||
|
||||
// The content should have relative paths
|
||||
var content = packageContents.Select(rp => rp.ToString(PathTable.StringTable)).ToList();
|
||||
var newHash = new PackageHashFile(weakPackageFingerprintHash.Hash.ToHex(), weakPackageFingerprint, content);
|
||||
TryUpdatePackageHashFile(loggingContext, package, GetPackageHashFile(packageTargetFolder), oldHash: null, newHash: newHash);
|
||||
|
||||
return Unit.Void;
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ namespace BuildXL.FrontEnd.Core
|
|||
private static ContentFingerprint CreateDownloadFingerprint(string baseText)
|
||||
{
|
||||
// In case something in the cached Bond data becomes incompatible, we must not match.
|
||||
const string VersionText = ", BondDataVersion=2;FingerprintVersion=5";
|
||||
const string VersionText = ", BondDataVersion=2;FingerprintVersion=1";
|
||||
var fingerprint = FingerprintUtilities.Hash(baseText + VersionText);
|
||||
return new ContentFingerprint(fingerprint);
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace BuildXL.FrontEnd.Core
|
|||
|
||||
// The file format change will force specs regeneration.
|
||||
// Change the version if the nuget spec generation has changed in a backward incompatible way.
|
||||
private const string HashFileFormatVersion = "10";
|
||||
private const string HashFileFormatVersion = "9";
|
||||
|
||||
/// <summary>
|
||||
/// The minimal number of lines for the hash file.
|
||||
|
|
|
@ -14,11 +14,9 @@ namespace Nuget {
|
|||
sources: globR(d`.`, "*.cs"),
|
||||
references: [
|
||||
...addIf(BuildXLSdk.isFullFramework,
|
||||
NetFx.Netstandard.dll,
|
||||
NetFx.System.IO.Compression.dll,
|
||||
NetFx.System.Xml.dll,
|
||||
NetFx.System.Xml.Linq.dll,
|
||||
NetFx.System.Net.Http.dll
|
||||
NetFx.Netstandard.dll
|
||||
),
|
||||
...addIf(BuildXLSdk.isFullFramework,
|
||||
importFrom("System.Memory").withQualifier({targetFramework: "netstandard2.0"}).pkg
|
||||
|
@ -35,7 +33,6 @@ namespace Nuget {
|
|||
importFrom("BuildXL.Engine").Processes.dll,
|
||||
importFrom("BuildXL.Pips").dll,
|
||||
importFrom("BuildXL.Utilities").dll,
|
||||
importFrom("BuildXL.Utilities").VstsAuthentication.dll,
|
||||
importFrom("BuildXL.Utilities").Collections.dll,
|
||||
importFrom("BuildXL.Utilities").Configuration.dll,
|
||||
importFrom("BuildXL.Utilities").Interop.dll,
|
||||
|
@ -45,21 +42,12 @@ namespace Nuget {
|
|||
importFrom("BuildXL.Utilities").Script.Constants.dll,
|
||||
|
||||
importFrom("Newtonsoft.Json").pkg,
|
||||
importFrom("NuGet.Versioning").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Protocol").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Configuration").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Common").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Frameworks").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Packaging").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Versioning").pkg,
|
||||
|
||||
...BuildXLSdk.tplPackages,
|
||||
],
|
||||
runtimeContent: [
|
||||
// Keep in sync with path at Public\Sdk\Public\Tools\NugetDownloader\Tool.NugetDownloader.dsc
|
||||
importFrom("BuildXL.Tools").NugetDownloader.dll
|
||||
],
|
||||
internalsVisibleTo: [
|
||||
"Test.BuildXL.FrontEnd.Nuget"
|
||||
],
|
||||
]
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using BuildXL.Utilities.Configuration;
|
||||
using static BuildXL.Utilities.FormattableStringEx;
|
||||
namespace BuildXL.FrontEnd.Nuget
|
||||
{
|
||||
/// <summary>
|
||||
/// Nuget invocation failed.
|
||||
/// </summary>
|
||||
public sealed class NugeInvocationtFailure : NugetFailure
|
||||
{
|
||||
private readonly INugetPackage m_package;
|
||||
private readonly string m_message;
|
||||
|
||||
/// <nodoc />
|
||||
public NugeInvocationtFailure(INugetPackage package, string message)
|
||||
: base(FailureType.PackageNotFound)
|
||||
{
|
||||
m_package = package;
|
||||
|
||||
m_message = message?.Trim();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Describe()
|
||||
{
|
||||
return I($"Package nuget://{m_package.Id}/{m_package.Version} could not be restored. {m_message}.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using BuildXL.Utilities.Configuration;
|
||||
using static BuildXL.Utilities.FormattableStringEx;
|
||||
namespace BuildXL.FrontEnd.Nuget
|
||||
{
|
||||
/// <summary>
|
||||
/// Nuget.exe failed with non-zero exit code.
|
||||
/// </summary>
|
||||
public sealed class NugetFailedWithNonZeroExitCodeFailure : NugetFailure
|
||||
{
|
||||
private readonly INugetPackage m_package;
|
||||
private readonly int m_exitCode;
|
||||
private readonly string m_stdOut;
|
||||
private readonly string m_stdErr;
|
||||
|
||||
/// <nodoc />
|
||||
public NugetFailedWithNonZeroExitCodeFailure(INugetPackage package, int exitCode, string stdOut, string stdErr)
|
||||
: base(FailureType.PackageNotFound)
|
||||
{
|
||||
m_package = package;
|
||||
m_exitCode = exitCode;
|
||||
|
||||
m_stdOut = stdOut?.Trim();
|
||||
m_stdErr = stdErr?.Trim();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Describe()
|
||||
{
|
||||
var separator = !string.IsNullOrEmpty(m_stdOut) && !string.IsNullOrEmpty(m_stdErr) ? Environment.NewLine : string.Empty;
|
||||
var output = I($"{m_stdOut}{separator}{m_stdErr}");
|
||||
return I($"Package nuget://{m_package.Id}/{m_package.Version} could not be restored because nuget.exe failed with exit code '{m_exitCode}'. \r\nTools output:\r\n{output}\r\nSee the buildxl log for more details.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.ContractsLight;
|
||||
using System.Linq;
|
||||
using BuildXL.Utilities;
|
||||
using BuildXL.Utilities.Configuration;
|
||||
using TypeScript.Net.Extensions;
|
||||
|
@ -22,21 +21,13 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
public readonly FailureType Type;
|
||||
|
||||
/// <nodoc />
|
||||
public readonly string Message;
|
||||
public readonly Exception Exception;
|
||||
|
||||
/// <nodoc />
|
||||
public NugetFailure(FailureType failureType, Exception e = null)
|
||||
{
|
||||
Type = failureType;
|
||||
|
||||
Message = GetAllMessages(e);
|
||||
}
|
||||
|
||||
/// <nodoc />
|
||||
public NugetFailure(FailureType failureType, string message)
|
||||
{
|
||||
Type = failureType;
|
||||
Message = message;
|
||||
Exception = e;
|
||||
}
|
||||
|
||||
/// <nodoc />
|
||||
|
@ -67,28 +58,20 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nuget invocation failure
|
||||
/// Nuget.exe failed to restore a package.
|
||||
/// </summary>
|
||||
public static NugetFailure CreateNugetInvocationFailure(INugetPackage package, Exception e)
|
||||
public static NugetFailure CreateNugetInvocationFailure(INugetPackage package, int exitCode, string stdOut, string stdErr)
|
||||
{
|
||||
return CreateNugetInvocationFailure(package, GetAllMessages(e));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Nuget invocation failure
|
||||
/// </summary>
|
||||
public static NugetFailure CreateNugetInvocationFailure(INugetPackage package, string message)
|
||||
{
|
||||
Contract.RequiresNotNull(package);
|
||||
Contract.RequiresNotNull(message);
|
||||
Contract.Requires(package != null);
|
||||
Contract.Requires(exitCode != 0);
|
||||
|
||||
// If the stdOut has the following text: 'NotFound http' or 'WARNING: Unable to find version', it means that the package name or version are not found.
|
||||
if (message.Contains("NotFound http") || message.Contains("WARNING: Unable to find version"))
|
||||
if (stdOut.Contains("NotFound http") || stdOut.Contains("WARNING: Unable to find version"))
|
||||
{
|
||||
return new CanNotFindPackageFailure(package);
|
||||
}
|
||||
|
||||
return new NugeInvocationtFailure(package, message);
|
||||
return new NugetFailedWithNonZeroExitCodeFailure(package, exitCode, stdOut, stdErr);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -106,10 +89,10 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
{
|
||||
if (Package != null)
|
||||
{
|
||||
return I($"Failed to retrieve nuget package '{Package.Id}' version '{Package.Version}' due to {Type.ToString()}. {Message}");
|
||||
return I($"Failed to retrieve nuget package '{Package.Id}' version '{Package.Version}' due to {Type.ToString()}. {Exception?.ToStringDemystified()}");
|
||||
}
|
||||
|
||||
return I($"Failed to process nuget packages due to {Type.ToString()}. {Message}");
|
||||
return I($"Failed to process nuget packages due to {Type.ToString()}. {Exception?.ToStringDemystified()}");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
@ -127,15 +110,30 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
/// <nodoc />
|
||||
public enum FailureType
|
||||
{
|
||||
/// <nodoc />
|
||||
FetchNugetExe,
|
||||
|
||||
/// <nodoc />
|
||||
FetchCredentialProvider,
|
||||
|
||||
/// <nodoc />
|
||||
WriteConfigFile,
|
||||
|
||||
/// <nodoc />
|
||||
WriteSpecFile,
|
||||
|
||||
/// <nodoc />
|
||||
CleanTargetFolder,
|
||||
|
||||
/// <nodoc />
|
||||
NugetFailedWithNonZeroExitCode,
|
||||
|
||||
/// <nodoc />
|
||||
NugetFailedWithIoException,
|
||||
|
||||
/// <nodoc />
|
||||
ListPackageContents,
|
||||
|
||||
/// <nodoc />
|
||||
ReadNuSpecFile,
|
||||
|
||||
|
@ -162,37 +160,6 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
/// <nodoc />
|
||||
UnhandledError,
|
||||
|
||||
/// <nodoc/>
|
||||
NoBaseAddressForRepository,
|
||||
}
|
||||
|
||||
private static string GetAllMessages(Exception e)
|
||||
{
|
||||
if (e is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (e is AggregateException aggregateException)
|
||||
{
|
||||
return string.Join(Environment.NewLine, aggregateException.Flatten().InnerExceptions.SelectMany(ie => GetInnerExceptions(ie)).Select(ex => ex.Message));
|
||||
}
|
||||
else
|
||||
{
|
||||
return string.Join(Environment.NewLine, GetInnerExceptions(e).Select(e => e.Message));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<Exception> GetInnerExceptions(Exception ex)
|
||||
{
|
||||
var innerException = ex;
|
||||
do
|
||||
{
|
||||
yield return innerException;
|
||||
innerException = innerException.InnerException;
|
||||
}
|
||||
while (innerException != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,14 +39,6 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
/// <nodoc />
|
||||
public MultiValueDictionary<Moniker, INugetPackage> DependenciesPerFramework { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Credential provider path that is used to retrieve the package
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// AbsolutePath.Invalid if none is to be used
|
||||
/// </remarks>
|
||||
public AbsolutePath CredentialProviderPath { get; }
|
||||
|
||||
/// <nodoc />
|
||||
public bool IsManagedPackage { get; set; }
|
||||
|
||||
|
@ -137,8 +129,7 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
NugetFrameworkMonikers nugetFrameworkMonikers,
|
||||
PackageOnDisk packageOnDisk,
|
||||
Dictionary<string, INugetPackage> packagesOnConfig,
|
||||
bool doNotEnforceDependencyVersions,
|
||||
AbsolutePath credentialProviderPath)
|
||||
bool doNotEnforceDependencyVersions)
|
||||
{
|
||||
m_context = context;
|
||||
PackageOnDisk = packageOnDisk;
|
||||
|
@ -151,7 +142,6 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
AssemblyToTargetFramework = new MultiValueDictionary<PathAtom, NugetTargetFramework>();
|
||||
m_dependencies = new List<INugetPackage>();
|
||||
DependenciesPerFramework = new MultiValueDictionary<PathAtom, INugetPackage>();
|
||||
CredentialProviderPath = credentialProviderPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -166,14 +156,13 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
[CanBeNull] XDocument nuSpec,
|
||||
PackageOnDisk packageOnDisk,
|
||||
Dictionary<string, INugetPackage> packagesOnConfig,
|
||||
bool doNotEnforceDependencyVersions,
|
||||
AbsolutePath credentialProviderPath)
|
||||
bool doNotEnforceDependencyVersions)
|
||||
{
|
||||
Contract.Requires(context != null);
|
||||
Contract.Requires(packageOnDisk != null);
|
||||
|
||||
var analyzedPackage = new NugetAnalyzedPackage(context, nugetFrameworkMonikers, packageOnDisk,
|
||||
packagesOnConfig, doNotEnforceDependencyVersions, credentialProviderPath);
|
||||
packagesOnConfig, doNotEnforceDependencyVersions);
|
||||
|
||||
analyzedPackage.ParseManagedSemantics();
|
||||
if (nuSpec != null && !analyzedPackage.TryParseDependenciesFromNuSpec(nuSpec))
|
||||
|
|
|
@ -41,7 +41,7 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
public IReadOnlyCollection<string> SupportedResolvers { get; } = new[] { WorkspaceNugetModuleResolver.NugetResolverName };
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ShouldRestrictBuildParameters { get; } = false;
|
||||
public bool ShouldRestrictBuildParameters { get; } = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void InitializeFrontEnd(FrontEndHost host, FrontEndContext context, IConfiguration configuration)
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.ContractsLight;
|
||||
using BuildXL.Utilities;
|
||||
|
||||
namespace BuildXL.FrontEnd.Nuget
|
||||
{
|
||||
/// <summary>
|
||||
/// The result of inspecting a package with <see cref="NugetPackageInspector"/>
|
||||
/// </summary>
|
||||
public readonly struct NugetInspectedPackage
|
||||
{
|
||||
/// <nodoc/>
|
||||
public readonly string Nuspec { get; }
|
||||
|
||||
/// <nodoc/>
|
||||
public readonly IReadOnlyList<RelativePath> Content { get; }
|
||||
|
||||
/// <nodoc/>
|
||||
public NugetInspectedPackage(string nuspec, IReadOnlyList<RelativePath> content)
|
||||
{
|
||||
Contract.RequiresNotNull(nuspec);
|
||||
Contract.RequiresNotNull(content);
|
||||
|
||||
Nuspec = nuspec;
|
||||
Content = content;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,365 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.ContractsLight;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildXL.FrontEnd.Nuget.Tracing;
|
||||
using BuildXL.Utilities;
|
||||
using BuildXL.Utilities.Configuration;
|
||||
using BuildXL.Utilities.Instrumentation.Common;
|
||||
using BuildXL.Utilities.VstsAuthentication;
|
||||
using NuGet.Configuration;
|
||||
using NuGet.Packaging;
|
||||
using NuGet.Protocol;
|
||||
|
||||
namespace BuildXL.FrontEnd.Nuget
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves NuGet package layouts and specs from a set of feeds
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Packages are not necessarily downloaded in full in order to inspect them, and partial downloads are always attempted first
|
||||
/// </remarks>
|
||||
public class NugetPackageInspector
|
||||
{
|
||||
// A 5K chunk looks reasonable as the initial download: most package central directory fits in 5K, so no extra requests are needed
|
||||
private const long MinimalChunkSizeInBytes = 5_000;
|
||||
// On every partial download iteration we linearly grow using a 20K base (i.e. 20, 40, 60, 80, etc.)
|
||||
private const long IncrementChunkSizeInBytes = 20_000;
|
||||
|
||||
private readonly CancellationToken m_cancellationToken;
|
||||
private readonly LoggingContext m_loggingContext;
|
||||
private readonly IEnumerable<(string repositoryName, Uri repositoryUri)> m_repositories;
|
||||
private readonly StringTable m_stringTable;
|
||||
private readonly Func<Possible<string>> m_discoverCredentialProvider;
|
||||
private IReadOnlyDictionary<Uri, PackageSourceCredential> m_packageBaseAddress;
|
||||
private readonly Lazy<Task<Possible<bool>>> m_initializationResult;
|
||||
|
||||
/// <nodoc/>
|
||||
public NugetPackageInspector(
|
||||
IEnumerable<(string repositoryName, Uri repositoryUri)> repositories,
|
||||
StringTable stringTable,
|
||||
Func<Possible<string>> discoverCredentialProvider,
|
||||
CancellationToken cancellationToken,
|
||||
Utilities.Instrumentation.Common.LoggingContext loggingContext)
|
||||
{
|
||||
Contract.RequiresNotNull(repositories);
|
||||
Contract.RequiresNotNull(discoverCredentialProvider);
|
||||
|
||||
m_cancellationToken = cancellationToken;
|
||||
m_loggingContext = loggingContext;
|
||||
m_repositories = repositories;
|
||||
m_stringTable = stringTable;
|
||||
m_discoverCredentialProvider = discoverCredentialProvider;
|
||||
|
||||
m_initializationResult = new Lazy<Task<Possible<bool>>>(async () => {
|
||||
|
||||
var logger = new StringBuilder();
|
||||
|
||||
try
|
||||
{
|
||||
return (await VSTSAuthenticationHelper.TryCreateSourceRepositories(m_repositories, m_discoverCredentialProvider, m_cancellationToken, logger))
|
||||
.Then<bool>(sourceRepositories =>
|
||||
{
|
||||
var packageBaseAddressMutable = new Dictionary<Uri, PackageSourceCredential>();
|
||||
foreach (var sourceRepository in sourceRepositories)
|
||||
{
|
||||
var serviceIndexResource = sourceRepository.GetResource<ServiceIndexResourceV3>(m_cancellationToken);
|
||||
|
||||
if (serviceIndexResource == null)
|
||||
{
|
||||
return new NugetFailure(NugetFailure.FailureType.NoBaseAddressForRepository, $"Cannot find index service for ${sourceRepository.PackageSource.SourceUri}");
|
||||
}
|
||||
|
||||
foreach (Uri packageBaseAddress in serviceIndexResource.GetServiceEntryUris(ServiceTypes.PackageBaseAddress))
|
||||
{
|
||||
packageBaseAddressMutable[packageBaseAddress] = sourceRepository.PackageSource.Credentials;
|
||||
}
|
||||
}
|
||||
|
||||
m_packageBaseAddress = packageBaseAddressMutable;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
}
|
||||
catch (Exception e) when (e is AggregateException || e is HttpRequestException)
|
||||
{
|
||||
return new NugetFailure(NugetFailure.FailureType.NoBaseAddressForRepository, e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
var log = logger.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(log))
|
||||
{
|
||||
Logger.Log.NuGetInspectionInitializationInfo(m_loggingContext, log);
|
||||
}
|
||||
}
|
||||
},
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether <see cref="TryInitAsync"/> has been succesfully called
|
||||
/// </summary>
|
||||
/// <remarks>Thread safe</remarks>
|
||||
public async Task<bool> IsInitializedAsync() => m_initializationResult.IsValueCreated && (await m_initializationResult.Value).Succeeded;
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the index sources of the specified repositories and initializes the base addresses
|
||||
/// </summary>
|
||||
/// <remarks>Thread safe. Subsequents initializations have no effect and return the same result as the first one did.</remarks>
|
||||
public Task<Possible<bool>> TryInitAsync() => m_initializationResult.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Tries to retrieve the layout and nuspec of the given package
|
||||
/// </summary>
|
||||
/// <remarks>Thread safe</remarks>
|
||||
public async Task<Possible<NugetInspectedPackage>> TryInspectAsync(INugetPackage identity)
|
||||
{
|
||||
Contract.Assert(await IsInitializedAsync(), "TryInitAsync() must be succesfully called first");
|
||||
|
||||
if (m_packageBaseAddress.Count == 0)
|
||||
{
|
||||
return new NugetFailure(NugetFailure.FailureType.NoBaseAddressForRepository);
|
||||
}
|
||||
|
||||
// Build the URI for the requested package and try to inspect it
|
||||
var packageIdLowerCase = identity.Id.ToLowerInvariant();
|
||||
var version = new NuGet.Versioning.NuGetVersion(identity.Version).ToNormalizedString();
|
||||
|
||||
Possible<NugetInspectedPackage> maybeInspectedPackage = default;
|
||||
foreach (var baseAddress in m_packageBaseAddress)
|
||||
{
|
||||
// URIs for retrieving the nuspec and the nupkg
|
||||
var packageUri = $"{baseAddress.Key.AbsoluteUri}{packageIdLowerCase}/{version}/{packageIdLowerCase}.{version}.nupkg";
|
||||
var nuspecUri = $"{baseAddress.Key.AbsoluteUri}{packageIdLowerCase}/{version}/{packageIdLowerCase}.nuspec";
|
||||
|
||||
maybeInspectedPackage = await TryInspectPackageAsync(identity, new Uri(packageUri), new Uri(nuspecUri), baseAddress.Value);
|
||||
if (maybeInspectedPackage.Succeeded)
|
||||
{
|
||||
return maybeInspectedPackage;
|
||||
}
|
||||
}
|
||||
|
||||
return maybeInspectedPackage.Failure;
|
||||
}
|
||||
|
||||
private async Task<Possible<NugetInspectedPackage>> TryInspectPackageAsync(INugetPackage identity, Uri nupkgUri, Uri nuspecUri, PackageSourceCredential packageSourceCredential)
|
||||
{
|
||||
AuthenticationHeaderValue authenticationHeader = null;
|
||||
|
||||
// If the download URI is pointing to a VSTS feed and we get a valid auth token, make it part of the request
|
||||
// We only want to send the token over HTTPS and to a VSTS domain to avoid security issues
|
||||
if (packageSourceCredential != null)
|
||||
{
|
||||
authenticationHeader = VSTSAuthenticationHelper.GetAuthenticationHeaderFromPAT(packageSourceCredential.PasswordText);
|
||||
}
|
||||
|
||||
// We want to be able to read the zip file central directory, where the layout of the package is. This is at the end of a zip file,
|
||||
// so we'll start requesting partial chunks of the content starting from the end and increasingly request more until we can understand
|
||||
// the zip central directory
|
||||
|
||||
int retries = 3;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var httpClient = new HttpClient())
|
||||
{
|
||||
httpClient.Timeout = TimeSpan.FromMinutes(1);
|
||||
|
||||
// Use authentication if defined
|
||||
if (authenticationHeader != null)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Accept.Clear();
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
httpClient.DefaultRequestHeaders.Authorization = authenticationHeader;
|
||||
}
|
||||
|
||||
// We need the nuspec file for analyzing the package
|
||||
var response = await httpClient.GetAsync(nuspecUri, m_cancellationToken);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
return NugetFailure.CreateNugetInvocationFailure(identity, response.ReasonPhrase);
|
||||
}
|
||||
|
||||
var nuspec = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// Now inspect the content of the nupkg
|
||||
return (await InspectContentAsync(identity, nupkgUri, authenticationHeader, httpClient)).Then(content => new NugetInspectedPackage(nuspec, content));
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is HttpRequestException || e is AggregateException || e is TaskCanceledException)
|
||||
{
|
||||
retries--;
|
||||
|
||||
if (retries == 0)
|
||||
{
|
||||
return NugetFailure.CreateNugetInvocationFailure(identity, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Possible<IReadOnlyList<RelativePath>>> InspectContentAsync(INugetPackage identity, Uri nupkgUri, AuthenticationHeaderValue authenticationHeader, HttpClient httpClient)
|
||||
{
|
||||
long? chunkStart = null;
|
||||
HttpResponseMessage response;
|
||||
bool forceFullDownload = false;
|
||||
var partialPackage = new MemoryStream();
|
||||
|
||||
try
|
||||
{
|
||||
// How many chunks we downloaded so far for the given package
|
||||
int chunkCount = 0;
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Only set a download range if we are not forcing a full download
|
||||
if (!forceFullDownload)
|
||||
{
|
||||
// Set the range header to retrieve a particular range of the content
|
||||
if (!chunkStart.HasValue)
|
||||
{
|
||||
// We don't know the total size yet, this is the first request. Start with MinimalChunkSizeInBytes.
|
||||
httpClient.DefaultRequestHeaders.Add("Range", "bytes=-" + MinimalChunkSizeInBytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is not the first time we request a chunk, and that means the content we retrieved son far is not enough to read the zip central directory.
|
||||
// So we already know where the chunk starts (and ends)
|
||||
httpClient.DefaultRequestHeaders.Add("Range", $"bytes={chunkStart}-{chunkStart + (chunkCount * IncrementChunkSizeInBytes) - 1}");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: redirect handling may be needed here
|
||||
response = await httpClient.GetAsync(nupkgUri, HttpCompletionOption.ResponseHeadersRead, m_cancellationToken);
|
||||
|
||||
// In the rare case where the initial chunk is bigger than the package, we might get this. Just force full download and retry
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.RequestedRangeNotSatisfiable)
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Remove("Range");
|
||||
forceFullDownload = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Check whether the service decided to ignore the partial request, and instead downloaded the whole thing
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.OK)
|
||||
{
|
||||
forceFullDownload = true;
|
||||
}
|
||||
|
||||
long totalLength = 0;
|
||||
if (!forceFullDownload)
|
||||
{
|
||||
totalLength = response.Content.Headers.ContentRange.Length.Value;
|
||||
|
||||
if (!chunkStart.HasValue)
|
||||
{
|
||||
// We just did the first request, chunk start points to MinimalChunkSizeInBytes from the end
|
||||
chunkStart = totalLength - MinimalChunkSizeInBytes;
|
||||
}
|
||||
}
|
||||
#if NET_COREAPP_60
|
||||
using (var chunk = await response.Content.ReadAsStreamAsync(m_cancellationToken))
|
||||
#else
|
||||
using (var chunk = await response.Content.ReadAsStreamAsync())
|
||||
#endif
|
||||
{
|
||||
// Unfortunately the .net framework does not support prepending a stream, so we do it manually
|
||||
// TODO: If this becomes a perf/footprint issue we could write a stream wrapper that knows how to compose streams. But
|
||||
// the expectation is that we shouldn't need to iterate over the same package for very long until we can read it
|
||||
partialPackage = await PrependToStreamAsync(chunk, partialPackage);
|
||||
}
|
||||
|
||||
// We don't want to get in the business of decoding a zip file. For that we use ZipArchive and check whether we get an exception
|
||||
// when we read it.
|
||||
// However, ZipArchive expects a stream with the proper length, since it does some validations about the location of the central
|
||||
// directory baed on that. To avoid creating a stream of the real size (some packages are GB big), we use a ZeroPaddedStream that
|
||||
// wraps the original stream but pretends to have the required size
|
||||
IReadOnlyCollection<ZipArchiveEntry> entries;
|
||||
try
|
||||
{
|
||||
var zf = new ZipArchive(forceFullDownload
|
||||
? partialPackage
|
||||
: new ZeroLeftPaddedStream(partialPackage, totalLength), ZipArchiveMode.Read);
|
||||
entries = zf.Entries;
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
// We downloaded the package in full but we cannot recognize it as a zip
|
||||
if (forceFullDownload)
|
||||
{
|
||||
return NugetFailure.CreateNugetInvocationFailure(identity, $"Cannot inspect package layout: {ex.ToStringDemystified()} ");
|
||||
}
|
||||
|
||||
// This check is just a heuristics for the rare case when we already downloaded more than 10% of the package total
|
||||
// size but we can't still read the zip central directory. At this point we can rather download the whole package
|
||||
if (partialPackage.Length < totalLength * .1)
|
||||
{
|
||||
chunkCount++;
|
||||
// We were not able to read the package central directory with what we downloaded so far. Request another chunk and
|
||||
// try again
|
||||
chunkStart -= chunkCount * IncrementChunkSizeInBytes;
|
||||
httpClient.DefaultRequestHeaders.Remove("Range");
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
httpClient.DefaultRequestHeaders.Remove("Range");
|
||||
forceFullDownload = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
return entries
|
||||
.Select(entry => entry.FullName.Contains('%') ? System.Net.WebUtility.UrlDecode(entry.FullName) : entry.FullName)
|
||||
.Where(entry => PackageHelper.IsPackageFile(entry, PackageSaveMode.Files | PackageSaveMode.Nuspec))
|
||||
.Select(entry => RelativePath.Create(m_stringTable, entry)).ToArray();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
partialPackage.Dispose();
|
||||
}
|
||||
|
||||
return NugetFailure.CreateNugetInvocationFailure(identity, $"Cannot inspect package layout: {response.RequestMessage} ");
|
||||
}
|
||||
|
||||
private async Task<MemoryStream> PrependToStreamAsync(Stream prefix, MemoryStream stream)
|
||||
{
|
||||
// If the destination stream is empty, just copy the prefix over
|
||||
if (stream.Length == 0)
|
||||
{
|
||||
await prefix.CopyToAsync(stream, m_cancellationToken);
|
||||
return stream;
|
||||
}
|
||||
|
||||
var tempPackage = new MemoryStream();
|
||||
await prefix.CopyToAsync(tempPackage, m_cancellationToken);
|
||||
|
||||
stream.Position = 0;
|
||||
await stream.CopyToAsync(tempPackage, m_cancellationToken);
|
||||
|
||||
stream.Dispose();
|
||||
|
||||
return tempPackage;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,8 +14,6 @@ using TypeScript.Net.Extensions;
|
|||
using TypeScript.Net.Types;
|
||||
using static TypeScript.Net.DScript.SyntaxFactory;
|
||||
using static BuildXL.FrontEnd.Nuget.SyntaxFactoryEx;
|
||||
using BuildXL.FrontEnd.Sdk;
|
||||
using BuildXL.FrontEnd.Script.Literals;
|
||||
|
||||
namespace BuildXL.FrontEnd.Nuget
|
||||
{
|
||||
|
@ -27,31 +25,22 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
private readonly PathTable m_pathTable;
|
||||
private readonly PackageOnDisk m_packageOnDisk;
|
||||
private readonly NugetAnalyzedPackage m_analyzedPackage;
|
||||
private readonly IReadOnlyDictionary<string, string> m_repositories;
|
||||
|
||||
private readonly NugetFrameworkMonikers m_nugetFrameworkMonikers;
|
||||
private readonly AbsolutePath m_sourceDirectory;
|
||||
|
||||
private readonly PathAtom m_xmlExtension;
|
||||
private readonly PathAtom m_pdbExtension;
|
||||
private readonly int? m_timeoutInMinutes;
|
||||
|
||||
/// <summary>Current spec generation format version</summary>
|
||||
public const int SpecGenerationFormatVersion = 12;
|
||||
public const int SpecGenerationFormatVersion = 11;
|
||||
|
||||
/// <nodoc />
|
||||
public NugetSpecGenerator(
|
||||
PathTable pathTable,
|
||||
NugetAnalyzedPackage analyzedPackage,
|
||||
IReadOnlyDictionary<string, string> repositories,
|
||||
AbsolutePath sourceDirectory,
|
||||
int? timeoutInMinutes = null)
|
||||
public NugetSpecGenerator(PathTable pathTable, NugetAnalyzedPackage analyzedPackage)
|
||||
{
|
||||
m_pathTable = pathTable;
|
||||
m_analyzedPackage = analyzedPackage;
|
||||
m_repositories = repositories;
|
||||
m_packageOnDisk = analyzedPackage.PackageOnDisk;
|
||||
m_nugetFrameworkMonikers = new NugetFrameworkMonikers(pathTable.StringTable);
|
||||
m_sourceDirectory = sourceDirectory;
|
||||
m_timeoutInMinutes = timeoutInMinutes;
|
||||
|
||||
m_xmlExtension = PathAtom.Create(pathTable.StringTable, ".xml");
|
||||
m_pdbExtension = PathAtom.Create(pathTable.StringTable, ".pdb");
|
||||
|
@ -64,13 +53,13 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
/// The generated format is:
|
||||
/// [optional] import of managed sdk core
|
||||
/// [optional] qualifier declaration
|
||||
/// const packageRoot = d`absolute path to the package roo`;
|
||||
/// @@public
|
||||
/// export const contents: StaticDirectory = NuGetDownloader.downloadPackage(
|
||||
/// {
|
||||
/// id: "package ID",
|
||||
/// version: "X.XX",
|
||||
/// ...
|
||||
/// }
|
||||
/// export const contents: StaticDirectory = Transformer.sealDirectory(
|
||||
/// packageRoot,
|
||||
/// [
|
||||
/// f`${packageRoot}/file`,
|
||||
/// ]);
|
||||
/// @@public
|
||||
/// export const pkg: NugetPackage = {contents ...};
|
||||
/// </remarks>
|
||||
|
@ -79,8 +68,8 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
{
|
||||
var sourceFileBuilder = new SourceFileBuilder();
|
||||
|
||||
// 0. Import * as NugetDownloader from "BuildXL.Tools.NugetDownloader" to be able to download NuGet packages
|
||||
sourceFileBuilder.Statement(ImportDeclaration("NugetDownloader", "BuildXL.Tools.NugetDownloader"));
|
||||
// 0. Import {Transformer} from "Sdk.Transformers" to be able to seal directories
|
||||
sourceFileBuilder.Statement(ImportDeclaration(new [] { "Transformer" }, "Sdk.Transformers"));
|
||||
|
||||
// 1. Optional import of managed sdk.
|
||||
if (analyzedPackage.IsManagedPackage)
|
||||
|
@ -99,7 +88,12 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
.SemicolonAndBlankLine();
|
||||
}
|
||||
|
||||
// Create a seal directory declaration with all the package content
|
||||
// 3. Declare a public directory that points to the package root, for convenience reasons
|
||||
sourceFileBuilder
|
||||
.Statement(new VariableDeclarationBuilder().Name("packageRoot").Initializer(PropertyAccess("Contents", "packageRoot")).Build())
|
||||
.SemicolonAndBlankLine();
|
||||
|
||||
// Create a sealed directory declaration with all the package content
|
||||
sourceFileBuilder
|
||||
.Statement(CreatePackageContents())
|
||||
.SemicolonAndBlankLine();
|
||||
|
@ -285,59 +279,24 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
private IStatement CreatePackageContents()
|
||||
{
|
||||
// Arguments for calling the nuget downloader SDK
|
||||
var downloadCallArgs = new List<(string, IExpression expression)>(4)
|
||||
{
|
||||
("id", new LiteralExpression(m_analyzedPackage.Id)),
|
||||
("version", new LiteralExpression(m_analyzedPackage.Version)),
|
||||
("downloadDirectory", Identifier("outputDir")),
|
||||
("extractedFiles", new ArrayLiteralExpression(m_analyzedPackage.PackageOnDisk.Contents
|
||||
.Select(relativePath => PathLikeLiteral(InterpolationKind.RelativePathInterpolation, relativePath.ToString(m_pathTable.StringTable, PathFormat.Script))))),
|
||||
("repositories", new ArrayLiteralExpression(m_repositories.Select(kvp => new ArrayLiteralExpression(new LiteralExpression(kvp.Key), new LiteralExpression(kvp.Value)))))
|
||||
};
|
||||
|
||||
// If a credential provider was used to inspect the package, pass it as an argument to be able to retrieve it.
|
||||
if (m_analyzedPackage.CredentialProviderPath.IsValid)
|
||||
{
|
||||
// If the credential provider is within the source tree, express it in terms of a mount, so the generated
|
||||
// spec is more resilient to cache hits across machines
|
||||
IExpression path;
|
||||
if (m_sourceDirectory.TryGetRelative(m_pathTable, m_analyzedPackage.CredentialProviderPath, out var relativeCredentialProviderPath))
|
||||
{
|
||||
path = PathLikeLiteral(
|
||||
InterpolationKind.FileInterpolation,
|
||||
new PropertyAccessExpression(new CallExpression(new PropertyAccessExpression("Context", "getMount"), new LiteralExpression("SourceRoot")), "path") ,
|
||||
"/" + relativeCredentialProviderPath.ToString(m_pathTable.StringTable, PathFormat.Script));
|
||||
}
|
||||
else
|
||||
{
|
||||
path = PathLikeLiteral(InterpolationKind.FileInterpolation, m_analyzedPackage.CredentialProviderPath.ToString(m_pathTable, PathFormat.Script));
|
||||
}
|
||||
|
||||
downloadCallArgs.Add(("credentialProviderPath", path));
|
||||
}
|
||||
|
||||
if (m_timeoutInMinutes != null)
|
||||
{
|
||||
downloadCallArgs.Add(("timeoutInMinutes", new LiteralExpression(m_timeoutInMinutes.Value)));
|
||||
}
|
||||
var relativepath = "../../../pkgs/" + m_packageOnDisk.Package.Id + "." + m_packageOnDisk.Package.Version;
|
||||
|
||||
return new ModuleDeclaration(
|
||||
"Contents",
|
||||
|
||||
Qualifier(new TypeLiteralNode()),
|
||||
|
||||
new VariableDeclarationBuilder().Name("outputDir").Visibility(Visibility.None).Type(new TypeReferenceNode("Directory")).Initializer(
|
||||
new CallExpression(new PropertyAccessExpression("Context", "getNewOutputDirectory"), new LiteralExpression("nuget"))).Build(),
|
||||
|
||||
PathLikeConstVariableDeclaration("packageRoot", InterpolationKind.DirectoryInterpolation, relativepath, Visibility.Export),
|
||||
new VariableDeclarationBuilder()
|
||||
.Name("all")
|
||||
.Visibility(Visibility.Public)
|
||||
.Type(new TypeReferenceNode("StaticDirectory"))
|
||||
.Initializer(
|
||||
new CallExpression(
|
||||
new PropertyAccessExpression("NugetDownloader", "downloadPackage"),
|
||||
ObjectLiteral(downloadCallArgs.ToArray())))
|
||||
new CallExpression(
|
||||
new PropertyAccessExpression("Transformer", "sealDirectory"),
|
||||
new Identifier("packageRoot"),
|
||||
new ArrayLiteralExpression(
|
||||
m_packageOnDisk.Contents.OrderBy(path => path.ToString(m_pathTable.StringTable)).Select(GetFileExpressionForPath)
|
||||
.ToArray())))
|
||||
.Build()
|
||||
);
|
||||
}
|
||||
|
@ -418,10 +377,11 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
private IExpression GetFileExpressionForPath(RelativePath relativePath)
|
||||
{
|
||||
// all.assertExistence(r`relativePath`)
|
||||
return new CallExpression(new PropertyAccessExpression("Contents", "all", "getFile"), PathLikeLiteral(
|
||||
InterpolationKind.RelativePathInterpolation,
|
||||
relativePath.ToString(m_pathTable.StringTable, PathFormat.Script)));
|
||||
// f`{packageRoot}/relativePath`
|
||||
return PathLikeLiteral(
|
||||
InterpolationKind.FileInterpolation,
|
||||
Identifier("packageRoot"),
|
||||
"/" + relativePath.ToString(m_pathTable.StringTable, PathFormat.Script));
|
||||
}
|
||||
|
||||
private IExpression CreateSimpleBinary(RelativePath binaryFile)
|
||||
|
|
|
@ -68,9 +68,7 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
out AbsolutePath nuSpecFile,
|
||||
out AbsolutePath moduleConfigFile)
|
||||
{
|
||||
// The nuspec file is always at the root of the package.
|
||||
nuSpecFile = packageDownloadResult.TargetLocation.Combine(pathTable, nuspecFileName);
|
||||
|
||||
nuSpecFile = AbsolutePath.Invalid;
|
||||
moduleConfigFile = AbsolutePath.Invalid;
|
||||
|
||||
foreach (var relativePath in packageDownloadResult.Contents)
|
||||
|
@ -79,7 +77,11 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
if (!relativePath.IsEmpty)
|
||||
{
|
||||
var fileName = relativePath.GetName();
|
||||
if (IsModuleConfigFileName(fileName, pathTable.StringTable))
|
||||
if (fileName.CaseInsensitiveEquals(pathTable.StringTable, nuspecFileName))
|
||||
{
|
||||
nuSpecFile = packageDownloadResult.TargetLocation.Combine(pathTable, relativePath);
|
||||
}
|
||||
else if (IsModuleConfigFileName(fileName, pathTable.StringTable))
|
||||
{
|
||||
moduleConfigFile = packageDownloadResult.TargetLocation.Combine(pathTable, relativePath);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,33 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
/// </summary>
|
||||
public static Logger Log { get; } = new LoggerImpl();
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.LaunchingNugetExe,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Verbose,
|
||||
Keywords = (int)Keywords.Diagnostics,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "Package nuget://{id}/{version} is being restored by launching nuget.exe with commandline: {commandline}")]
|
||||
public abstract void LaunchingNugetExe(LoggingContext context, string id, string version, string commandline);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.CredentialProviderRequiresToolUrl,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Error,
|
||||
Keywords = (ushort)(Keywords.UserMessage | Keywords.UserError),
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "CredentialProviders for the Nuget resolver are required to specify ToolUrl. '{toolName}' does not do so.")]
|
||||
public abstract void CredentialProviderRequiresToolUrl(LoggingContext context, string toolName);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetDownloadInvalidHash,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Warning,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "Configured downloadHash '{hash}' for tool '{toolName}' is invalid. Attempt to download tool without hash guard.")]
|
||||
public abstract void NugetDownloadInvalidHash(LoggingContext context, string toolName, string hash);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedToCleanTargetFolder,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
|
@ -33,13 +60,74 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
public abstract void NugetFailedToCleanTargetFolder(LoggingContext context, string id, string version, string targetLocation, string message);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetInspectionInitialization,
|
||||
(ushort)LogEventId.NugetFailedWithNonZeroExitCode,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Error,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message =
|
||||
"Package nuget://{id}/{version} could not be downloaded because nuget.exe failed with exit code '{exitcode}'. \r\nTools output:\r\n{output}\r\nSee the buildxl log for more details.")]
|
||||
public abstract void NugetFailedWithNonZeroExitCode(LoggingContext context, string id, string version, int exitcode, string output);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedDueToSomeWellKnownIssue,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Error,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message =
|
||||
"Package nuget://{id}/{version} could not be restored. {message}\r\nSee the buildxl log for more details.")]
|
||||
public abstract void NugetFailedDueToSomeWellKnownIssue(LoggingContext context, string id, string version, string message);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedWithNonZeroExitCodeDetailed,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Verbose,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "Nuget inspection info: {message}")]
|
||||
public abstract void NuGetInspectionInitializationInfo(LoggingContext context, string message);
|
||||
Message =
|
||||
"Package nuget://{id}/{version} could not be downloaded because nuget.exe failed with exit code '{exitcode}'. The standard output was:\r\n{stdOut}\r\n. The standard Error was:\r\n{stdErr}")]
|
||||
public abstract void NugetFailedWithNonZeroExitCodeDetailed(
|
||||
LoggingContext context,
|
||||
string id,
|
||||
string version,
|
||||
int exitcode,
|
||||
string stdOut,
|
||||
string stdErr);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedToListPackageContents,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Error,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message =
|
||||
"Package nuget://{id}/{version} could not be downloaded because we could not list the content of the folder '{packageFolder}': {message}")]
|
||||
public abstract void NugetFailedToListPackageContents(LoggingContext context, string id, string version, string packageFolder, string message);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedToWriteConfigurationFile,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Error,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "Could not be write configuration file to '{configFile}': {message}")]
|
||||
public abstract void NugetFailedToWriteConfigurationFile(LoggingContext context, string configFile, string message);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedToWriteConfigurationFileForPackage,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Error,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message =
|
||||
"Package nuget://{id}/{version} could not be processed because we could not write configuration file to '{configFile}': {message}")]
|
||||
public abstract void NugetFailedToWriteConfigurationFileForPackage(
|
||||
LoggingContext context,
|
||||
string id,
|
||||
string version,
|
||||
string configFile,
|
||||
string message);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedToWriteSpecFileForPackage,
|
||||
|
@ -81,6 +169,21 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
"Package nuget://{id}/{version} could not be processed because the nuspec file cannot be found at '{expectedPath}'.")]
|
||||
public abstract void NugetFailedNuSpecFileNotFound(LoggingContext context, string id, string version, string expectedPath);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetFailedWithInvalidNuSpecXml,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Error,
|
||||
Keywords = (ushort)(Keywords.UserMessage | Keywords.UserError),
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message =
|
||||
"Package nuget://{id}/{version} could not be processed because the nuspec file at: '{nuspecFile}' has unexpected xml contents: {illegalXmlElement}")]
|
||||
public abstract void NugetFailedWithInvalidNuSpecXml(
|
||||
LoggingContext context,
|
||||
string id,
|
||||
string version,
|
||||
string nuspecFile,
|
||||
string illegalXmlElement);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetPackageVersionIsInvalid,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
|
@ -90,6 +193,16 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
Message = "Invalid nuget version '{version}' found in config.dsc for package '{packageName}'. Expected version format is 'A.B.C.D'.")]
|
||||
public abstract void ConfigNugetPackageVersionIsInvalid(LoggingContext context, string version, string packageName);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetLaunchFailed,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Verbose,
|
||||
Keywords = (ushort)Keywords.UserMessage,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message =
|
||||
"Package nuget://{id}/{version} could not be downloaded because nuget.exe could not be launched: {message}")]
|
||||
public abstract void NugetLaunchFailed(LoggingContext context, string id, string version, string message);
|
||||
|
||||
[GeneratedEvent(
|
||||
(ushort)LogEventId.NugetStatistics,
|
||||
EventGenerators = EventGenerators.LocalOnly,
|
||||
|
@ -105,7 +218,7 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Informational,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "Inspecting NuGet packages ({packagesDownloadedCount} of {totalPackagesCount} done).",
|
||||
Message = "Restoring NuGet packages ({packagesDownloadedCount} of {totalPackagesCount} done).",
|
||||
Keywords = (int)Keywords.UserMessage | (int)Keywords.Overwritable)]
|
||||
public abstract void NugetPackageDownloadedCount(LoggingContext context, long packagesDownloadedCount, long totalPackagesCount);
|
||||
|
||||
|
@ -114,7 +227,7 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Informational,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "Inspected {totalPackagesCount} NuGet packages in {totalMilliseconds}ms.",
|
||||
Message = "Restored {totalPackagesCount} NuGet packages in {totalMilliseconds}ms.",
|
||||
Keywords = (int)Keywords.UserMessage | (int)Keywords.Overwritable)]
|
||||
public abstract void NugetPackagesAreRestored(LoggingContext context, long totalPackagesCount, long totalMilliseconds);
|
||||
|
||||
|
@ -141,7 +254,7 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
EventGenerators = EventGenerators.LocalOnly,
|
||||
EventLevel = Level.Verbose,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message = "Package nuget://{id}/{version} could not be inspected because of an unhandled error '{error}'.",
|
||||
Message = "Package nuget://{id}/{version} could not be restored because of an unhandled error '{error}'.",
|
||||
Keywords = (int)Keywords.UserMessage | (int)Keywords.Overwritable)]
|
||||
public abstract void NugetUnhandledError(LoggingContext context, string id, string version, string error);
|
||||
|
||||
|
@ -151,7 +264,7 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
EventLevel = Level.Informational,
|
||||
EventTask = (ushort)Tasks.Parser,
|
||||
Message =
|
||||
"Inspecting NuGet packages ({packagesDownloadedCount} of {totalPackagesCount} done). Remaining {packagesToDownloadDetail}",
|
||||
"Restoring NuGet packages ({packagesDownloadedCount} of {totalPackagesCount} done). Remaining {packagesToDownloadDetail}",
|
||||
Keywords = (int)Keywords.UserMessage | (int)Keywords.OverwritableOnly)]
|
||||
public abstract void NugetPackageDownloadedCountWithDetails(LoggingContext context, long packagesDownloadedCount,
|
||||
long totalPackagesCount, string packagesToDownloadDetail);
|
||||
|
|
|
@ -14,41 +14,40 @@ namespace BuildXL.FrontEnd.Nuget.Tracing
|
|||
None = 0,
|
||||
|
||||
// reserved 11300 .. 11400 for nuget
|
||||
// Reserved LaunchingNugetExe = 11300,
|
||||
// Reserved CredentialProviderRequiresToolUrl = 11301,
|
||||
// Reserved NugetDownloadInvalidHash = 11302,
|
||||
NugetFailedToCleanTargetFolder = 11303,
|
||||
// Reserved NugetFailedWithNonZeroExitCode = 11304,
|
||||
// Reserved NugetFailedWithNonZeroExitCodeDetailed = 11305,
|
||||
// Reserved NugetFailedToListPackageContents = 11306,
|
||||
// Reserved NugetFailedToWriteConfigurationFile = 11307,
|
||||
// Reserved NugetFailedToWriteConfigurationFileForPackage = 11308,
|
||||
NugetFailedToWriteSpecFileForPackage = 11309,
|
||||
NugetFailedToReadNuSpecFile = 11310,
|
||||
// Reserved NugetFailedWithInvalidNuSpecXml = 11311,
|
||||
NugetPackageVersionIsInvalid = 11312,
|
||||
// Reserved NugetLaunchFailed = 11313,
|
||||
NugetStatistics = 11314,
|
||||
NugetPackagesRestoredCount = 11315,
|
||||
NugetPackagesAreRestored = 11316,
|
||||
NugetDependencyVersionWasNotSpecifiedButConfigOneWasChosen = 11317,
|
||||
NugetDependencyVersionWasPickedWithinRange = 11318,
|
||||
NugetDependencyVersionDoesNotMatch = 11319,
|
||||
NugetUnknownFramework = 11320,
|
||||
NugetPackageDownloadDetails = 11321,
|
||||
NugetFailedNuSpecFileNotFound = 11322,
|
||||
// Reserved NugetFailedDueToSomeWellKnownIssue = 11323,
|
||||
NugetRegenerateNugetSpecs = 11324,
|
||||
NugetRegeneratingNugetSpecs = 11325,
|
||||
NugetUnhandledError = 11326,
|
||||
ForcePopulateTheCacheOptionWasSpecified = 11327,
|
||||
NugetConcurrencyLevel = 11328,
|
||||
UsePackagesFromDisOptionWasSpecified = 11329,
|
||||
NugetFailedDownloadPackagesAndGenerateSpecs = 11330,
|
||||
NugetFailedDownloadPackage = 11331,
|
||||
NugetFailedGenerationResultFromDownloadedPackage = 11332,
|
||||
NugetFailedToWriteGeneratedSpecStateFile = 11333,
|
||||
NugetCannotReuseSpecOnDisk = 11334,
|
||||
NugetInspectionInitialization = 11335,
|
||||
LaunchingNugetExe = 11300,
|
||||
CredentialProviderRequiresToolUrl,
|
||||
NugetDownloadInvalidHash,
|
||||
NugetFailedToCleanTargetFolder,
|
||||
NugetFailedWithNonZeroExitCode,
|
||||
NugetFailedWithNonZeroExitCodeDetailed,
|
||||
NugetFailedToListPackageContents,
|
||||
NugetFailedToWriteConfigurationFile,
|
||||
NugetFailedToWriteConfigurationFileForPackage,
|
||||
NugetFailedToWriteSpecFileForPackage,
|
||||
NugetFailedToReadNuSpecFile,
|
||||
NugetFailedWithInvalidNuSpecXml,
|
||||
NugetPackageVersionIsInvalid,
|
||||
NugetLaunchFailed,
|
||||
NugetStatistics,
|
||||
NugetPackagesRestoredCount,
|
||||
NugetPackagesAreRestored,
|
||||
NugetDependencyVersionWasNotSpecifiedButConfigOneWasChosen,
|
||||
NugetDependencyVersionWasPickedWithinRange,
|
||||
NugetDependencyVersionDoesNotMatch,
|
||||
NugetUnknownFramework,
|
||||
NugetPackageDownloadDetails,
|
||||
NugetFailedNuSpecFileNotFound,
|
||||
NugetFailedDueToSomeWellKnownIssue,
|
||||
NugetRegenerateNugetSpecs,
|
||||
NugetRegeneratingNugetSpecs,
|
||||
NugetUnhandledError,
|
||||
ForcePopulateTheCacheOptionWasSpecified,
|
||||
NugetConcurrencyLevel,
|
||||
UsePackagesFromDisOptionWasSpecified,
|
||||
NugetFailedDownloadPackagesAndGenerateSpecs,
|
||||
NugetFailedDownloadPackage,
|
||||
NugetFailedGenerationResultFromDownloadedPackage,
|
||||
NugetFailedToWriteGeneratedSpecStateFile,
|
||||
NugetCannotReuseSpecOnDisk,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,8 +62,6 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
private const string SpecGenerationVersionFileSuffix = ".version";
|
||||
|
||||
private const string NugetCredentialProviderEnv = "NUGET_CREDENTIALPROVIDERS_PATH";
|
||||
|
||||
private NugetFrameworkMonikers m_nugetFrameworkMonikers;
|
||||
|
||||
// These are set during Initialize
|
||||
|
@ -84,6 +82,8 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
private NugetResolverOutputLayout m_resolverOutputLayout;
|
||||
private IConfiguration m_configuration;
|
||||
|
||||
private readonly Lazy<AbsolutePath> m_nugetToolFolder;
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Kind => KnownResolverKind.NugetResolverKind;
|
||||
|
||||
|
@ -101,6 +101,10 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
m_embeddedSpecsResolver = new WorkspaceSourceModuleResolver(stringTable, statistics, logger: null);
|
||||
|
||||
m_useMonoBasedNuGet = OperatingSystemHelper.IsUnixOS;
|
||||
|
||||
m_nugetToolFolder = new Lazy<AbsolutePath>(
|
||||
() => m_host.GetFolderForFrontEnd(NugetResolverName).Combine(PathTable, "nuget"),
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
|
@ -389,58 +393,21 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
private Task<Possible<NugetGenerationResult>> DownloadPackagesAndGenerateSpecsIfNeededInternal()
|
||||
{
|
||||
return m_nugetGenerationResult.GetOrCreate(this, @this => Task.FromResult(@this.DownloadPackagesAndGenerateSpecs()));
|
||||
return m_nugetGenerationResult.GetOrCreate(this, @this => @this.DownloadPackagesAndGenerateSpecsAsync());
|
||||
}
|
||||
|
||||
private Possible<AbsolutePath> TryResolveCredentialProvider()
|
||||
private AbsolutePath GetNugetToolFolder()
|
||||
{
|
||||
if (!m_host.Engine.TryGetBuildParameter(NugetCredentialProviderEnv, nameof(NugetFrontEnd), out string credentialProvidersPaths))
|
||||
{
|
||||
return new NugetFailure(NugetFailure.FailureType.FetchCredentialProvider, $"Environment variable {NugetCredentialProviderEnv} is not set");
|
||||
}
|
||||
|
||||
// Here we do something slightly simpler than what NuGet does and just look for the first credential
|
||||
// provider we can find
|
||||
AbsolutePath credentialProviderPath = AbsolutePath.Invalid;
|
||||
foreach (string path in credentialProvidersPaths.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (!AbsolutePath.TryCreate(m_context.PathTable, path, out var absolutePath))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Use the engine to enumerate, since the result of the enumeration should be sensitive
|
||||
// to the graph building process
|
||||
credentialProviderPath = m_host.Engine.EnumerateFiles(absolutePath, "CredentialProvider*.exe").FirstOrDefault();
|
||||
if (credentialProviderPath.IsValid)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!credentialProviderPath.IsValid)
|
||||
{
|
||||
return new NugetFailure(NugetFailure.FailureType.FetchCredentialProvider, $"Unable to authenticate using a credential provider: Credential provider was not found under '{credentialProvidersPaths}'.");
|
||||
}
|
||||
|
||||
// We want to rebuild the build graph if the credential provider changed. Running the whole auth process under detours sounds like too much,
|
||||
// let's just read the credential provider main .exe so presence/hash gets recorded instead.
|
||||
m_host.Engine.TryGetFrontEndFile(credentialProviderPath, nameof(NugetFrontEnd), out _);
|
||||
|
||||
return m_host.Engine.Translate(credentialProviderPath);
|
||||
return m_nugetToolFolder.Value;
|
||||
}
|
||||
|
||||
private Possible<NugetGenerationResult> DownloadPackagesAndGenerateSpecs()
|
||||
private AbsolutePath GetNugetConfigPath()
|
||||
{
|
||||
var maybeCredentialProviderPath = TryResolveCredentialProvider();
|
||||
|
||||
var nugetInspector = new NugetPackageInspector(
|
||||
m_resolverSettings.Repositories.Select(kvp => (kvp.Key, new Uri(kvp.Value))),
|
||||
PathTable.StringTable,
|
||||
() => maybeCredentialProviderPath.Then(path => path.ToString(PathTable)),
|
||||
m_context.CancellationToken,
|
||||
m_context.LoggingContext);
|
||||
return GetNugetToolFolder().Combine(PathTable, "NuGet.config");
|
||||
}
|
||||
|
||||
private async Task<Possible<NugetGenerationResult>> DownloadPackagesAndGenerateSpecsAsync()
|
||||
{
|
||||
// Log if the full package restore is requested.
|
||||
if (m_configuration.FrontEnd.ForcePopulatePackageCache())
|
||||
{
|
||||
|
@ -462,6 +429,26 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
using (var nugetEndToEndStopWatch = m_statistics.EndToEnd.Start())
|
||||
{
|
||||
var possiblePaths = await TryDownloadNugetAsync(m_resolverSettings.Configuration, GetNugetToolFolder());
|
||||
if (!possiblePaths.Succeeded)
|
||||
{
|
||||
Logger.Log.NugetFailedDownloadPackagesAndGenerateSpecs(m_context.LoggingContext, possiblePaths.Failure.DescribeIncludingInnerFailures());
|
||||
return possiblePaths.Failure;
|
||||
}
|
||||
|
||||
var possibleNugetConfig = CreateNuGetConfig(m_repositories);
|
||||
|
||||
var possibleNuGetConfig = TryWriteXmlConfigFile(
|
||||
package: null,
|
||||
targetFile: GetNugetConfigPath(),
|
||||
xmlDoc: possibleNugetConfig.Result);
|
||||
|
||||
if (!possibleNuGetConfig.Succeeded)
|
||||
{
|
||||
Logger.Log.NugetFailedDownloadPackagesAndGenerateSpecs(m_context.LoggingContext, possibleNuGetConfig.Failure.DescribeIncludingInnerFailures());
|
||||
return possibleNuGetConfig.Failure;
|
||||
}
|
||||
|
||||
// Will contain all packages successfully downloaded and analyzed
|
||||
var restoredPackagesById = new Dictionary<string, NugetAnalyzedPackage>();
|
||||
|
||||
|
@ -481,11 +468,6 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
{
|
||||
var aggregateResult = new Possible<NugetAnalyzedPackage>[nugetProgress.Length];
|
||||
|
||||
if (!m_host.Engine.TryGetBuildParameter(NugetCredentialProviderEnv, nameof(NugetFrontEnd), out string allCredentialProviderPaths))
|
||||
{
|
||||
allCredentialProviderPaths = string.Empty;
|
||||
}
|
||||
|
||||
Parallel.For(fromInclusive: 0,
|
||||
toExclusive: aggregateResult.Length,
|
||||
new ParallelOptions()
|
||||
|
@ -493,20 +475,9 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
MaxDegreeOfParallelism = concurrencyLevel,
|
||||
CancellationToken = m_context.CancellationToken,
|
||||
},
|
||||
(index, state) =>
|
||||
(index) =>
|
||||
{
|
||||
aggregateResult[index] = TryInspectPackageAsync(
|
||||
nugetProgress[index],
|
||||
maybeCredentialProviderPath.Succeeded ? maybeCredentialProviderPath.Result : AbsolutePath.Invalid,
|
||||
allCredentialProviderPaths,
|
||||
nugetInspector).GetAwaiter().GetResult();
|
||||
|
||||
// Let's not schedule more work in the parallel for if one of the inspections failed
|
||||
if (!aggregateResult[index].Succeeded)
|
||||
{
|
||||
state.Stop();
|
||||
}
|
||||
|
||||
aggregateResult[index] = TryRestorePackageAsync(nugetProgress[index], possiblePaths.Result).GetAwaiter().GetResult();
|
||||
return;
|
||||
});
|
||||
|
||||
|
@ -578,12 +549,12 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
{
|
||||
if (m_configuration.DoesSourceDiskDriveHaveSeekPenalty(PathTable))
|
||||
{
|
||||
nugetConcurrency = Environment.ProcessorCount;
|
||||
nugetConcurrency = Environment.ProcessorCount / 2;
|
||||
message = I($"Lowering restore package concurrency to {nugetConcurrency} because a source drive is on HDD.");
|
||||
}
|
||||
else
|
||||
{
|
||||
nugetConcurrency = Math.Min(128, Environment.ProcessorCount * 4);
|
||||
nugetConcurrency = Math.Min(16, Environment.ProcessorCount * 2);
|
||||
message = I($"Increasing restore package concurrency to {nugetConcurrency} because a source drive is on SSD.");
|
||||
}
|
||||
}
|
||||
|
@ -593,11 +564,9 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
return nugetConcurrency;
|
||||
}
|
||||
|
||||
private async Task<Possible<NugetAnalyzedPackage>> TryInspectPackageAsync(
|
||||
private async Task<Possible<NugetAnalyzedPackage>> TryRestorePackageAsync(
|
||||
NugetProgress progress,
|
||||
AbsolutePath selectedCredentialProviderPath,
|
||||
string allCredentialProviderPaths,
|
||||
NugetPackageInspector nugetInspector)
|
||||
IReadOnlyList<AbsolutePath> credentialProviderPaths)
|
||||
{
|
||||
progress.StartRunning();
|
||||
|
||||
|
@ -610,9 +579,11 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
var layout = NugetPackageOutputLayout.Create(
|
||||
PathTable,
|
||||
package,
|
||||
nugetTool: credentialProviderPaths[0],
|
||||
nugetConfig: GetNugetConfigPath(),
|
||||
resolverLayout: m_resolverOutputLayout);
|
||||
|
||||
var possiblePkg = await TryInpectPackageWithCache(package, progress, layout, allCredentialProviderPaths, nugetInspector);
|
||||
var possiblePkg = await TryRestorePackageWithCache(package, progress, layout, credentialProviderPaths);
|
||||
|
||||
if (!possiblePkg.Succeeded)
|
||||
{
|
||||
|
@ -625,7 +596,6 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
var analyzedPackage = AnalyzeNugetPackage(
|
||||
possiblePkg.Result,
|
||||
selectedCredentialProviderPath,
|
||||
m_resolverSettings.DoNotEnforceDependencyVersions);
|
||||
if (!analyzedPackage.Succeeded)
|
||||
{
|
||||
|
@ -810,6 +780,60 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
return true;
|
||||
}
|
||||
|
||||
private Possible<XDocument, NugetFailure> CreateNuGetConfig(IReadOnlyDictionary<string, string> repositories)
|
||||
{
|
||||
XElement credentials = null;
|
||||
if (m_useMonoBasedNuGet)
|
||||
{
|
||||
var localNuGetConfigPath = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
|
||||
".config",
|
||||
"NuGet",
|
||||
"NuGet.Config");
|
||||
|
||||
try
|
||||
{
|
||||
// Sadly nuget goes all over the disk to chain configs, but when it comes to the credentials it decides not to properly merge them.
|
||||
// So for now we have to hack and read the credentials from the users profile and stick them in the local config....
|
||||
if (FileUtilities.Exists(localNuGetConfigPath))
|
||||
{
|
||||
ExceptionUtilities.HandleRecoverableIOException(
|
||||
() =>
|
||||
{
|
||||
var doc = XDocument.Load(localNuGetConfigPath);
|
||||
credentials = doc.Element("configuration")?.Element("packageSourceCredentials");
|
||||
},
|
||||
e => throw new BuildXLException($"Failed to load nuget config {localNuGetConfigPath}", e));
|
||||
}
|
||||
}
|
||||
catch (BuildXLException e)
|
||||
{
|
||||
Logger.Log.NugetFailedToWriteConfigurationFile(
|
||||
m_context.LoggingContext,
|
||||
localNuGetConfigPath,
|
||||
e.LogEventMessage);
|
||||
return new NugetFailure(NugetFailure.FailureType.WriteConfigFile, e.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
return new XDocument(
|
||||
new XElement(
|
||||
"configuration",
|
||||
new XElement(
|
||||
"packageRestore",
|
||||
new XElement("clear"),
|
||||
new XElement("add", new XAttribute("key", "enabled"), new XAttribute("value", "True"))),
|
||||
new XElement(
|
||||
"disabledPackageSources",
|
||||
new XElement("clear")),
|
||||
credentials,
|
||||
new XElement(
|
||||
"packageSources",
|
||||
new XElement("clear"),
|
||||
repositories.Select(
|
||||
kv => new XElement("add", new XAttribute("key", kv.Key), new XAttribute("value", kv.Value))))));
|
||||
}
|
||||
|
||||
private Possible<AbsolutePath> TryWriteSourceFile(INugetPackage package, AbsolutePath targetFile, ISourceFile sourceFile)
|
||||
{
|
||||
Contract.Requires(package != null);
|
||||
|
@ -849,6 +873,45 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
return targetFile;
|
||||
}
|
||||
|
||||
private Possible<AbsolutePath> TryWriteXmlConfigFile(INugetPackage package, AbsolutePath targetFile, XDocument xmlDoc)
|
||||
{
|
||||
var targetFilePath = targetFile.ToString(PathTable);
|
||||
|
||||
try
|
||||
{
|
||||
FileUtilities.CreateDirectory(Path.GetDirectoryName(targetFilePath));
|
||||
ExceptionUtilities.HandleRecoverableIOException(
|
||||
() =>
|
||||
xmlDoc.Save(targetFilePath, SaveOptions.DisableFormatting),
|
||||
e =>
|
||||
{
|
||||
throw new BuildXLException("Cannot save document", e);
|
||||
});
|
||||
}
|
||||
catch (BuildXLException e)
|
||||
{
|
||||
if (package == null)
|
||||
{
|
||||
Logger.Log.NugetFailedToWriteConfigurationFile(m_context.LoggingContext, targetFilePath, e.LogEventMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Log.NugetFailedToWriteConfigurationFileForPackage(
|
||||
m_context.LoggingContext,
|
||||
package.Id,
|
||||
package.Version,
|
||||
targetFilePath,
|
||||
e.LogEventMessage);
|
||||
}
|
||||
|
||||
return new NugetFailure(package, NugetFailure.FailureType.WriteConfigFile, e.InnerException);
|
||||
}
|
||||
|
||||
m_host.Engine.RecordFrontEndFile(targetFile, NugetResolverName);
|
||||
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
private AbsolutePath GetPackageSpecDir(NugetAnalyzedPackage analyzedPackage)
|
||||
{
|
||||
return m_resolverOutputLayout.GeneratedSpecsFolder
|
||||
|
@ -946,8 +1009,7 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
// No-op if the directory exists
|
||||
FileUtilities.CreateDirectory(packageSpecDirStr);
|
||||
|
||||
var nugetSpecGenerator = new NugetSpecGenerator(PathTable, analyzedPackage, m_resolverSettings.Repositories,
|
||||
m_configuration.Layout.SourceDirectory, m_resolverSettings.Configuration.DownloadTimeoutMin);
|
||||
var nugetSpecGenerator = new NugetSpecGenerator(PathTable, analyzedPackage);
|
||||
|
||||
var possibleProjectFile = TryWriteSourceFile(
|
||||
analyzedPackage.PackageOnDisk.Package,
|
||||
|
@ -982,7 +1044,6 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
|
||||
internal Possible<NugetAnalyzedPackage> AnalyzeNugetPackage(
|
||||
PackageOnDisk packageOnDisk,
|
||||
AbsolutePath credentialProviderPath,
|
||||
bool doNotEnforceDependencyVersions)
|
||||
{
|
||||
Contract.Requires(packageOnDisk != null);
|
||||
|
@ -996,7 +1057,7 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
}
|
||||
|
||||
var result = NugetAnalyzedPackage.TryAnalyzeNugetPackage(m_context, m_nugetFrameworkMonikers, maybeNuspecXdoc.Result,
|
||||
packageOnDisk, m_packageRegistry.AllPackagesById, doNotEnforceDependencyVersions, credentialProviderPath);
|
||||
packageOnDisk, m_packageRegistry.AllPackagesById, doNotEnforceDependencyVersions);
|
||||
|
||||
if (result == null)
|
||||
{
|
||||
|
@ -1058,7 +1119,8 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
}
|
||||
}
|
||||
|
||||
private string CreateRestoreFingerPrint(INugetPackage package, string credentialProviderPaths)
|
||||
|
||||
private string CreateRestoreFingerPrint(INugetPackage package, IEnumerable<AbsolutePath> credentialProviderPaths)
|
||||
{
|
||||
var fingerprintParams = new List<string>
|
||||
{
|
||||
|
@ -1068,7 +1130,7 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
};
|
||||
if (credentialProviderPaths != null)
|
||||
{
|
||||
fingerprintParams.Add("cred=" + credentialProviderPaths);
|
||||
fingerprintParams.Add("cred=" + UppercaseSortAndJoinStrings(credentialProviderPaths.Select(p => p.ToString(PathTable))));
|
||||
}
|
||||
|
||||
return "nuget://" + string.Join("&", fingerprintParams);
|
||||
|
@ -1087,12 +1149,11 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
return restoreFingerPrint + "&" + string.Join("&", fingerprintParams);
|
||||
}
|
||||
|
||||
private async Task<Possible<PackageOnDisk>> TryInpectPackageWithCache(
|
||||
private async Task<Possible<PackageOnDisk>> TryRestorePackageWithCache(
|
||||
INugetPackage package,
|
||||
NugetProgress progress,
|
||||
NugetPackageOutputLayout layout,
|
||||
string credentialProviderPaths,
|
||||
NugetPackageInspector nugetInspector)
|
||||
IEnumerable<AbsolutePath> credentialProviderPaths)
|
||||
{
|
||||
var packageRestoreFingerprint = CreateRestoreFingerPrint(package, credentialProviderPaths);
|
||||
var identity = PackageIdentity.Nuget(package.Id, package.Version, package.Alias);
|
||||
|
@ -1104,23 +1165,10 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
packageRestoreFingerprint,
|
||||
identity,
|
||||
layout.PackageFolder,
|
||||
layout.PathToNuspec,
|
||||
async () =>
|
||||
() =>
|
||||
{
|
||||
progress.StartDownloadFromNuget();
|
||||
|
||||
// We want to delay initialization until the first inspection that is actually needed
|
||||
// Initializing the inspector involves resolving the index service for each specified repository
|
||||
if (!await nugetInspector.IsInitializedAsync())
|
||||
{
|
||||
var initResult = await nugetInspector.TryInitAsync();
|
||||
if (!initResult.Succeeded)
|
||||
{
|
||||
return initResult.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
return await TryInspectPackage(package, layout, nugetInspector);
|
||||
return TryDownloadPackage(package, layout, credentialProviderPaths);
|
||||
});
|
||||
|
||||
return maybePackage.Then(downloadResult =>
|
||||
|
@ -1135,71 +1183,34 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
return string.Join(",", values.Select(s => s.ToUpperInvariant()).OrderBy(s => s));
|
||||
}
|
||||
|
||||
private async Task<Possible<IReadOnlyList<RelativePath>>> TryInspectPackage(
|
||||
INugetPackage package,
|
||||
NugetPackageOutputLayout layout,
|
||||
NugetPackageInspector nugetInspector)
|
||||
private async Task<Possible<IReadOnlyList<RelativePath>>> TryDownloadPackage(
|
||||
INugetPackage package, NugetPackageOutputLayout layout, IEnumerable<AbsolutePath> credentialProviderPaths)
|
||||
{
|
||||
var xmlConfigResult = TryWriteXmlConfigFile(package, layout.PackagesConfigFile, GetPackagesXml(package));
|
||||
if (!xmlConfigResult.Succeeded)
|
||||
{
|
||||
return xmlConfigResult.Failure;
|
||||
}
|
||||
|
||||
var cleanUpResult = TryCleanupPackagesFolder(package, layout);
|
||||
if (!cleanUpResult.Succeeded)
|
||||
{
|
||||
return cleanUpResult.Failure;
|
||||
}
|
||||
|
||||
// Inspect the package (get nuspec and layout)
|
||||
var maybeInspectedPackage = await nugetInspector.TryInspectAsync(package);
|
||||
if (!maybeInspectedPackage.Succeeded)
|
||||
var nugetExeResult = await TryLaunchNugetExeAsync(package, layout, credentialProviderPaths);
|
||||
if (!nugetExeResult.Succeeded)
|
||||
{
|
||||
return maybeInspectedPackage.Failure;
|
||||
return nugetExeResult.Failure;
|
||||
}
|
||||
|
||||
var inspectedPackage = maybeInspectedPackage.Result;
|
||||
|
||||
// Serialize the nuspec to disk. In this way we can also use the bxl cache to avoid
|
||||
// downloading this content again. The hash file will contain the layout, which is serialized
|
||||
// later
|
||||
try
|
||||
var contentResult = TryEnumerateDirectory(package, layout.PackageDirectory);
|
||||
if (!contentResult.Succeeded)
|
||||
{
|
||||
#if NET_FRAMEWORK
|
||||
return ExceptionUtilities.HandleRecoverableIOException(
|
||||
() =>
|
||||
#else
|
||||
return await ExceptionUtilities.HandleRecoverableIOException(
|
||||
async () =>
|
||||
#endif
|
||||
{
|
||||
FileUtilities.CreateDirectoryWithRetry(layout.PackageFolder.ToString(PathTable));
|
||||
|
||||
// XML files need to be serialized with the right enconding, so let's use XDocument
|
||||
// for that
|
||||
var xdocument = XDocument.Parse(inspectedPackage.Nuspec);
|
||||
|
||||
using (var nuspec = new FileStream(layout.PathToNuspec.ToString(PathTable), FileMode.Create))
|
||||
{
|
||||
#if NET_FRAMEWORK
|
||||
xdocument.Save(nuspec);
|
||||
#else
|
||||
await xdocument.SaveAsync(nuspec, SaveOptions.None, m_context.CancellationToken);
|
||||
#endif
|
||||
}
|
||||
|
||||
return new Possible<IReadOnlyList<RelativePath>>(inspectedPackage.Content);
|
||||
},
|
||||
e =>
|
||||
{
|
||||
throw new BuildXLException("Cannot write package's nuspec file to disk", e);
|
||||
});
|
||||
}
|
||||
catch (BuildXLException e)
|
||||
{
|
||||
Logger.Log.NugetFailedToWriteSpecFileForPackage(
|
||||
m_context.LoggingContext,
|
||||
package.Id,
|
||||
package.Version,
|
||||
layout.PathToNuspec.ToString(PathTable),
|
||||
e.LogEventMessage);
|
||||
return new NugetFailure(package, NugetFailure.FailureType.WriteSpecFile, e.InnerException);
|
||||
return contentResult.Failure;
|
||||
}
|
||||
|
||||
return contentResult.Result;
|
||||
}
|
||||
|
||||
private Possible<Unit> TryCleanupPackagesFolder(INugetPackage package, NugetPackageOutputLayout layout)
|
||||
|
@ -1244,6 +1255,466 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<Possible<Unit>> TryLaunchNugetExeAsync(INugetPackage package, NugetPackageOutputLayout layout, IEnumerable<AbsolutePath> credentialProviderPaths)
|
||||
{
|
||||
var fileAccessManifest = GenerateFileAccessManifest(layout, credentialProviderPaths);
|
||||
|
||||
var buildParameters = BuildParameters
|
||||
.GetFactory()
|
||||
.PopulateFromEnvironment();
|
||||
|
||||
var tool = layout.NugetTool.ToString(PathTable);
|
||||
|
||||
var argumentsBuilder = new StringBuilder();
|
||||
if (m_useMonoBasedNuGet)
|
||||
{
|
||||
argumentsBuilder.AppendFormat("\"{0}\"", tool);
|
||||
argumentsBuilder.Append(" ");
|
||||
|
||||
if (!buildParameters.ToDictionary().TryGetValue("MONO_HOME", out var monoHome))
|
||||
{
|
||||
return new NugetFailure(package, NugetFailure.FailureType.MissingMonoHome);
|
||||
}
|
||||
|
||||
tool = Path.Combine(monoHome, "mono");
|
||||
}
|
||||
|
||||
// TODO:escape quotes properly
|
||||
argumentsBuilder
|
||||
.AppendFormat("restore \"{0}\"", layout.PackagesConfigFile.ToString(PathTable))
|
||||
.AppendFormat(" -OutputDirectory \"{0}\"", layout.PackageRootFolder.ToString(PathTable))
|
||||
.Append(" -Verbosity detailed")
|
||||
.AppendFormat(" -ConfigFile \"{0}\"", layout.NugetConfig.ToString(PathTable))
|
||||
.Append(" -PackageSaveMode nuspec")
|
||||
.Append(" -NoCache")
|
||||
// Currently we have to hack nuget to MsBuild version 4 which should come form the current CLR.
|
||||
.Append(" -MsBuildVersion 4");
|
||||
|
||||
if (!m_host.Configuration.Interactive)
|
||||
{
|
||||
// Prevent Nuget from showing any UI when not in interactive mode
|
||||
argumentsBuilder.Append(" -NonInteractive");
|
||||
}
|
||||
|
||||
var arguments = argumentsBuilder.ToString();
|
||||
|
||||
Logger.Log.LaunchingNugetExe(m_context.LoggingContext, package.Id, package.Version, tool + " " + arguments);
|
||||
try
|
||||
{
|
||||
// For NugetFrontEnd always create a new ConHost process.
|
||||
// The NugetFrontEnd is normally executed only once, so the overhead is low.
|
||||
// Also the NugetFrontEnd is a really long running process, so creating the ConHost is relatively
|
||||
// very cheap. It provides guarantee if the process pollutes the ConHost env,
|
||||
// it will not affect the server ConHost.
|
||||
var info =
|
||||
new SandboxedProcessInfo(
|
||||
m_context.PathTable,
|
||||
new NugetFileStorage(layout.PackageTmpDirectory),
|
||||
tool,
|
||||
fileAccessManifest,
|
||||
disableConHostSharing: true,
|
||||
ContainerConfiguration.DisabledIsolation,
|
||||
loggingContext: m_context.LoggingContext,
|
||||
sandboxConnection: m_useMonoBasedNuGet ? new SandboxConnectionFake() : null)
|
||||
{
|
||||
Arguments = arguments,
|
||||
WorkingDirectory = layout.TempDirectory.ToString(PathTable),
|
||||
PipSemiStableHash = 0,
|
||||
PipDescription = "NuGet FrontEnd",
|
||||
EnvironmentVariables = GetNugetEnvironmentVariables(),
|
||||
Timeout = TimeSpan.FromMinutes(20), // Limit the time nuget has to download each nuget package
|
||||
};
|
||||
|
||||
return await RetryOnFailure(
|
||||
runNuget: async () =>
|
||||
{
|
||||
var process = await SandboxedProcessFactory.StartAsync(info, forceSandboxing: !m_useMonoBasedNuGet);
|
||||
var result = await process.GetResultAsync();
|
||||
return (result, result.ExitCode == 0);
|
||||
},
|
||||
onError: async result =>
|
||||
{
|
||||
// Log the result before trying again
|
||||
var (stdOut, stdErr) = await GetStandardOutAndError(result);
|
||||
|
||||
Logger.Log.NugetFailedWithNonZeroExitCodeDetailed(
|
||||
m_context.LoggingContext,
|
||||
package.Id,
|
||||
package.Version,
|
||||
result.ExitCode,
|
||||
stdOut,
|
||||
stdErr);
|
||||
|
||||
return (stdOut, stdErr);
|
||||
},
|
||||
onFinalFailure: (exitCode, stdOut, stdErr) =>
|
||||
{
|
||||
// Give up and fail
|
||||
return NugetFailure.CreateNugetInvocationFailure(package, exitCode, stdOut, stdErr);
|
||||
});
|
||||
}
|
||||
catch (BuildXLException e)
|
||||
{
|
||||
Logger.Log.NugetLaunchFailed(m_context.LoggingContext, package.Id, package.Version, e.LogEventMessage);
|
||||
return new NugetFailure(package, NugetFailure.FailureType.NugetFailedWithIoException);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return new NugetFailure(package, NugetFailure.FailureType.NugetFailedWithIoException, e);
|
||||
}
|
||||
|
||||
async Task<(string stdOut, string stdErr)> GetStandardOutAndError(SandboxedProcessResult result)
|
||||
{
|
||||
try
|
||||
{
|
||||
await result.StandardOutput.SaveAsync();
|
||||
var stdOut = await result.StandardOutput.ReadValueAsync();
|
||||
|
||||
await result.StandardError.SaveAsync();
|
||||
var stdErr = await result.StandardError.ReadValueAsync();
|
||||
|
||||
return (stdOut, stdErr);
|
||||
}
|
||||
catch (BuildXLException e)
|
||||
{
|
||||
return (e.LogEventMessage, string.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
BuildParameters.IBuildParameters GetNugetEnvironmentVariables()
|
||||
{
|
||||
// the environment variable names below should use the casing appropriate for the target OS
|
||||
// (on Windows it won't matter, but on Unix-like systems, including Cygwin environment on Windows,
|
||||
// it matters, and has to be all upper-cased). See also doc comment for IBuildParameters.Select
|
||||
return buildParameters
|
||||
.Select(
|
||||
new[]
|
||||
{
|
||||
"ComSpec",
|
||||
"PATH",
|
||||
"PATHEXT",
|
||||
"NUMBER_OF_PROCESSORS",
|
||||
"OS",
|
||||
"PROCESSOR_ARCHITECTURE",
|
||||
"PROCESSOR_IDENTIFIER",
|
||||
"PROCESSOR_LEVEL",
|
||||
"PROCESSOR_REVISION",
|
||||
"SystemDrive",
|
||||
"SystemRoot",
|
||||
"SYSTEMTYPE",
|
||||
"NUGET_CREDENTIALPROVIDERS_PATH",
|
||||
"__CLOUDBUILD_AUTH_HELPER_CONFIG__",
|
||||
"__Q_DPAPI_Secrets_Dir",
|
||||
|
||||
// Nuget Credential Provider env variables
|
||||
"1ESSHAREDASSETS_BUILDXL_FEED_PAT",
|
||||
"CLOUDBUILD_BUILDXL_SELFHOST_FEED_PAT",
|
||||
|
||||
// Auth material needed for low-privilege build.
|
||||
"QAUTHMATERIALROOT",
|
||||
|
||||
// Used by the artifacts credential provider. See here for more information on how this variable is configured - https://github.com/microsoft/artifacts-credprovider#azure-devops-server
|
||||
"VSS_NUGET_EXTERNAL_FEED_ENDPOINTS"
|
||||
})
|
||||
.Override(
|
||||
new Dictionary<string, string>()
|
||||
{
|
||||
{"TMP", layout.TempDirectoryAsString},
|
||||
{"TEMP", layout.TempDirectoryAsString},
|
||||
{"NUGET_PACKAGES", layout.TempDirectoryAsString},
|
||||
{"NUGET_ROOT", layout.TempDirectoryAsString},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class SandboxConnectionFake : ISandboxConnection
|
||||
{
|
||||
public SandboxKind Kind => SandboxKind.MacOsKext;
|
||||
|
||||
public int NumberOfKextConnections => 1;
|
||||
|
||||
public ulong MinReportQueueEnqueueTime { get; set; }
|
||||
|
||||
public TimeSpan CurrentDrought
|
||||
{
|
||||
get
|
||||
{
|
||||
var nowNs = Sandbox.GetMachAbsoluteTime();
|
||||
var minReportTimeNs = MinReportQueueEnqueueTime;
|
||||
return TimeSpan.FromTicks(nowNs > minReportTimeNs ? (long)((nowNs - minReportTimeNs) / 100) : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public bool IsInTestMode => true;
|
||||
|
||||
public bool NotifyUsage(uint cpuUsage, uint availableRamMB) { return true; }
|
||||
|
||||
public bool NotifyPipStarted(LoggingContext loggingContext, FileAccessManifest fam, SandboxedProcessUnix process) { return true; }
|
||||
|
||||
public IEnumerable<(string, string)> AdditionalEnvVarsToSet(long pipId)
|
||||
{
|
||||
return Enumerable.Empty<(string, string)>();
|
||||
}
|
||||
|
||||
public void NotifyPipProcessTerminated(long pipId, int processId) { }
|
||||
|
||||
public void NotifyRootProcessExited(long pipId, SandboxedProcessUnix process) { }
|
||||
|
||||
public bool NotifyPipFinished(long pipId, SandboxedProcessUnix process) { return true; }
|
||||
|
||||
public void ReleaseResources() { }
|
||||
}
|
||||
|
||||
private static async Task<Possible<Unit>> RetryOnFailure(
|
||||
Func<Task<(SandboxedProcessResult result, bool isPassed)>> runNuget,
|
||||
Func<SandboxedProcessResult, Task<(string, string)>> onError,
|
||||
Func<int, string, string, Possible<Unit>> onFinalFailure,
|
||||
int retryCount = MaxRetryCount,
|
||||
int retryDelayMs = RetryDelayMs)
|
||||
{
|
||||
Contract.Assert(retryCount >= 1, "Maximum retry count must be greater than or equal to one. Found " + retryCount);
|
||||
|
||||
var pass = false;
|
||||
var iteration = 1;
|
||||
|
||||
while (!pass)
|
||||
{
|
||||
var tuple = await runNuget();
|
||||
var result = tuple.result;
|
||||
pass = tuple.isPassed;
|
||||
|
||||
if (!pass)
|
||||
{
|
||||
var (stdOut, stdErr) = await onError(result);
|
||||
|
||||
if (iteration >= retryCount)
|
||||
{
|
||||
return onFinalFailure(result.ExitCode, stdOut, stdErr);
|
||||
}
|
||||
|
||||
// Try again!
|
||||
iteration++;
|
||||
|
||||
await Task.Delay(retryDelayMs);
|
||||
}
|
||||
}
|
||||
|
||||
return Unit.Void;
|
||||
}
|
||||
|
||||
private FileAccessManifest GenerateFileAccessManifest(NugetPackageOutputLayout layout, IEnumerable<AbsolutePath> credentialProviderPaths)
|
||||
{
|
||||
var fileAccessManifest = new FileAccessManifest(PathTable)
|
||||
{
|
||||
// TODO: If this is set to true, then NuGet will fail if TMG Forefront client is running.
|
||||
// Filtering out in SandboxedProcessReport won't work because Detours already blocks the access to FwcWsp.dll.
|
||||
// Almost all machines in Office run TMG Forefront client.
|
||||
// So far for WDG, FailUnexpectedFileAccesses is false due to allowlists.
|
||||
// As a consequence, the file access manifest below gets nullified.
|
||||
FailUnexpectedFileAccesses = false,
|
||||
ReportFileAccesses = true,
|
||||
MonitorNtCreateFile = true,
|
||||
MonitorZwCreateOpenQueryFile = true,
|
||||
};
|
||||
|
||||
fileAccessManifest.AddScope(layout.TempDirectory, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation);
|
||||
fileAccessManifest.AddScope(layout.PackageRootFolder, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation);
|
||||
if (!OperatingSystemHelper.IsUnixOS)
|
||||
{
|
||||
fileAccessManifest.AddScope(
|
||||
AbsolutePath.Create(PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.Windows)),
|
||||
FileAccessPolicy.MaskAll,
|
||||
FileAccessPolicy.AllowAllButSymlinkCreation);
|
||||
}
|
||||
|
||||
fileAccessManifest.AddPath(layout.NugetTool, values: FileAccessPolicy.AllowRead, mask: FileAccessPolicy.MaskNothing);
|
||||
fileAccessManifest.AddPath(layout.NugetToolExeConfig, values: FileAccessPolicy.AllowReadIfNonexistent, mask: FileAccessPolicy.MaskNothing);
|
||||
fileAccessManifest.AddPath(layout.NugetConfig, values: FileAccessPolicy.AllowRead, mask: FileAccessPolicy.MaskNothing);
|
||||
fileAccessManifest.AddPath(layout.PackagesConfigFile, values: FileAccessPolicy.AllowRead, mask: FileAccessPolicy.MaskNothing);
|
||||
|
||||
// Nuget is picky
|
||||
fileAccessManifest.AddScope(layout.ResolverFolder, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation);
|
||||
|
||||
// Nuget fails if it can't access files in the users (https://github.com/NuGet/Home/issues/2676) profile.
|
||||
// We'll have to explicitly set all config in our nuget.config file and override anything the user can set and allow the read here.
|
||||
var roamingAppDataNuget = AbsolutePath.Create(PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.ApplicationData)).Combine(PathTable, "NuGet");
|
||||
fileAccessManifest.AddScope(roamingAppDataNuget, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation);
|
||||
|
||||
var localAppDataNuget = AbsolutePath.Create(PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)).Combine(PathTable, "NuGet");
|
||||
fileAccessManifest.AddScope(localAppDataNuget, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation);
|
||||
|
||||
// Nuget also probes in ProgramData on the machine.
|
||||
var commonAppDataNuget = AbsolutePath.Create(PathTable, SpecialFolderUtilities.GetFolderPath(Environment.SpecialFolder.CommonApplicationData)).Combine(PathTable, "NuGet");
|
||||
fileAccessManifest.AddScope(commonAppDataNuget, FileAccessPolicy.MaskAll, FileAccessPolicy.AllowAllButSymlinkCreation);
|
||||
|
||||
foreach (var providerPath in credentialProviderPaths)
|
||||
{
|
||||
fileAccessManifest.AddPath(providerPath, values: FileAccessPolicy.AllowRead, mask: FileAccessPolicy.MaskNothing);
|
||||
fileAccessManifest.AddPath(
|
||||
providerPath.ChangeExtension(PathTable, PathAtom.Create(PathTable.StringTable, ".exe.config")),
|
||||
values: FileAccessPolicy.AllowRead,
|
||||
mask: FileAccessPolicy.MaskNothing);
|
||||
}
|
||||
|
||||
return fileAccessManifest;
|
||||
}
|
||||
|
||||
private static XDocument GetPackagesXml(INugetPackage package)
|
||||
{
|
||||
|
||||
var version = package.Version;
|
||||
var plusIndex = version.IndexOf('+');
|
||||
if (plusIndex > 0)
|
||||
{
|
||||
version = version.Substring(0, plusIndex);
|
||||
}
|
||||
return new XDocument(
|
||||
new XElement(
|
||||
"packages",
|
||||
new XElement(
|
||||
"package",
|
||||
new XAttribute("id", package.Id),
|
||||
new XAttribute("version", version))));
|
||||
}
|
||||
|
||||
private Possible<List<RelativePath>, NugetFailure> TryEnumerateDirectory(INugetPackage package, string packagePath)
|
||||
{
|
||||
var enumerateDirectoryResult = EnumerateDirectoryRecursively(packagePath, out var contents);
|
||||
|
||||
if (!enumerateDirectoryResult.Succeeded)
|
||||
{
|
||||
var message = enumerateDirectoryResult.GetNativeErrorMessage();
|
||||
Logger.Log.NugetFailedToListPackageContents(m_context.LoggingContext, package.Id, package.Version, packagePath, message);
|
||||
return new NugetFailure(package, NugetFailure.FailureType.ListPackageContents, enumerateDirectoryResult.CreateExceptionForError());
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
private EnumerateDirectoryResult EnumerateDirectoryRecursively(string packagePath, out List<RelativePath> resultingContent)
|
||||
{
|
||||
resultingContent = new List<RelativePath>();
|
||||
return EnumerateDirectoryRecursively(RelativePath.Empty, resultingContent);
|
||||
|
||||
EnumerateDirectoryResult EnumerateDirectoryRecursively(RelativePath relativePath, List<RelativePath> contents)
|
||||
{
|
||||
var result = FileUtilities.EnumerateDirectoryEntries(
|
||||
Path.Combine(packagePath, relativePath.ToString(m_context.StringTable)),
|
||||
(name, attr) =>
|
||||
{
|
||||
var nestedRelativePath = relativePath.Combine(PathAtom.Create(m_context.StringTable, name));
|
||||
if ((attr & FileAttributes.Directory) != 0)
|
||||
{
|
||||
EnumerateDirectoryRecursively(nestedRelativePath, contents);
|
||||
}
|
||||
else
|
||||
{
|
||||
contents.Add(nestedRelativePath);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<Possible<AbsolutePath[]>> TryDownloadNugetAsync(INugetConfiguration configuration, AbsolutePath targetFolder)
|
||||
{
|
||||
configuration = configuration ?? new NugetConfiguration();
|
||||
|
||||
var downloads = new Task<Possible<ContentHash>>[1 + configuration.CredentialProviders.Count];
|
||||
var paths = new AbsolutePath[downloads.Length];
|
||||
|
||||
var nugetTargetLocation = targetFolder.Combine(m_context.PathTable, "nuget.exe");
|
||||
|
||||
var nugetLocation = configuration.ToolUrl;
|
||||
if (string.IsNullOrEmpty(nugetLocation))
|
||||
{
|
||||
var version = configuration.Version ?? "latest";
|
||||
nugetLocation = string.Format(CultureInfo.InvariantCulture, "https://dist.nuget.org/win-x86-commandline/{0}/nuget.exe", version);
|
||||
}
|
||||
|
||||
TryGetExpectedContentHash(configuration, out var expectedHash);
|
||||
|
||||
downloads[0] = m_host.DownloadFile(nugetLocation, nugetTargetLocation, expectedHash, NugetResolverName);
|
||||
paths[0] = nugetTargetLocation;
|
||||
|
||||
for (var i = 0; i < configuration.CredentialProviders.Count; i++)
|
||||
{
|
||||
var credentialProvider = configuration.CredentialProviders[i];
|
||||
var credentialProviderName = NugetResolverName + ".credentialProvider." + i.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
TryGetExpectedContentHash(credentialProvider, out var expectedProviderHash);
|
||||
|
||||
var toolUrl = credentialProvider.ToolUrl;
|
||||
if (string.IsNullOrEmpty(toolUrl))
|
||||
{
|
||||
// TODO: Have better provenance for configuration values.
|
||||
Logger.Log.CredentialProviderRequiresToolUrl(m_context.LoggingContext, credentialProviderName);
|
||||
return new NugetFailure(NugetFailure.FailureType.FetchCredentialProvider);
|
||||
}
|
||||
|
||||
var fileNameStart = toolUrl.LastIndexOfAny(new[] { '/', '\\' });
|
||||
var fileName = fileNameStart >= 0 ? toolUrl.Substring(fileNameStart + 1) : toolUrl;
|
||||
var targetLocation = targetFolder.Combine(m_context.PathTable, fileName);
|
||||
downloads[i + 1] = m_host.DownloadFile(toolUrl, targetLocation, expectedProviderHash, credentialProviderName);
|
||||
paths[i + 1] = targetLocation;
|
||||
}
|
||||
|
||||
var results = await Task.WhenAll(downloads);
|
||||
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
return result.Failure;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/// <nodoc />
|
||||
private bool TryGetExpectedContentHash(IArtifactLocation artifactLocation, out ContentHash? expectedHash)
|
||||
{
|
||||
expectedHash = null;
|
||||
if (!string.IsNullOrEmpty(artifactLocation.Hash))
|
||||
{
|
||||
if (!ContentHash.TryParse(artifactLocation.Hash, out var contentHash))
|
||||
{
|
||||
// TODO: better provenance for configuration settings.
|
||||
Logger.Log.NugetDownloadInvalidHash(
|
||||
m_context.LoggingContext,
|
||||
"nuget.exe",
|
||||
artifactLocation.Hash);
|
||||
return false;
|
||||
}
|
||||
|
||||
expectedHash = contentHash;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <nodoc />
|
||||
private sealed class NugetFileStorage : ISandboxedProcessFileStorage
|
||||
{
|
||||
private readonly string m_directory;
|
||||
|
||||
/// <nodoc />
|
||||
public NugetFileStorage(string directory)
|
||||
{
|
||||
m_directory = directory;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public string GetFileName(SandboxedProcessFile file)
|
||||
{
|
||||
return Path.Combine(m_directory, file.DefaultFileName());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class that holds all folders required for a nuget resolver
|
||||
/// </summary>
|
||||
|
@ -1282,30 +1753,50 @@ namespace BuildXL.FrontEnd.Nuget
|
|||
{
|
||||
private readonly NugetResolverOutputLayout m_resolverLayout;
|
||||
|
||||
public NugetPackageOutputLayout(PathTable pathTable, INugetPackage package, NugetResolverOutputLayout resolverLayout)
|
||||
public NugetPackageOutputLayout(PathTable pathTable, INugetPackage package, AbsolutePath nugetTool, AbsolutePath nugetConfig, NugetResolverOutputLayout resolverLayout)
|
||||
{
|
||||
m_resolverLayout = resolverLayout;
|
||||
Package = package;
|
||||
NugetTool = nugetTool;
|
||||
NugetConfig = nugetConfig;
|
||||
|
||||
var idAndVersion = package.Id + "." + package.Version;
|
||||
|
||||
NugetToolExeConfig = nugetTool.ChangeExtension(pathTable, PathAtom.Create(pathTable.StringTable, ".exe.config"));
|
||||
// All the folders should include version to avoid potential race conditions during nuget execution.
|
||||
PackageFolder = PackageRootFolder.Combine(pathTable, idAndVersion);
|
||||
PackageDirectory = PackageFolder.ToString(pathTable);
|
||||
PackageTmpDirectory = TempDirectory.Combine(pathTable, idAndVersion).ToString(pathTable);
|
||||
PathToNuspec = PackageFolder.Combine(pathTable, $"{package.Id}.nuspec");
|
||||
PackagesConfigFile = ConfigRootFolder.Combine(pathTable, idAndVersion).Combine(pathTable, "packages.config");
|
||||
}
|
||||
|
||||
public static NugetPackageOutputLayout Create(PathTable pathTable, INugetPackage package,
|
||||
NugetResolverOutputLayout resolverLayout)
|
||||
AbsolutePath nugetTool, AbsolutePath nugetConfig, NugetResolverOutputLayout resolverLayout)
|
||||
{
|
||||
return new NugetPackageOutputLayout(pathTable, package, resolverLayout);
|
||||
return new NugetPackageOutputLayout(pathTable, package, nugetTool, nugetConfig, resolverLayout);
|
||||
}
|
||||
|
||||
public INugetPackage Package { get; }
|
||||
|
||||
public AbsolutePath ResolverFolder => m_resolverLayout.ResolverFolder;
|
||||
|
||||
public string ResolverDirectory => m_resolverLayout.ResolverDirectory;
|
||||
|
||||
public AbsolutePath NugetTool { get; }
|
||||
|
||||
public AbsolutePath NugetToolExeConfig { get; }
|
||||
|
||||
public AbsolutePath NugetConfig { get; }
|
||||
|
||||
public AbsolutePath TempDirectory => m_resolverLayout.TempDirectory;
|
||||
|
||||
public string TempDirectoryAsString => m_resolverLayout.TempDirectoryAsString;
|
||||
|
||||
public AbsolutePath PackageRootFolder => m_resolverLayout.PackageRootFolder;
|
||||
|
||||
public AbsolutePath PathToNuspec { get; }
|
||||
public AbsolutePath ConfigRootFolder => m_resolverLayout.ConfigRootFolder;
|
||||
|
||||
public AbsolutePath PackagesConfigFile { get; }
|
||||
|
||||
public AbsolutePath PackageFolder { get; }
|
||||
|
||||
|
|
|
@ -181,8 +181,6 @@ namespace BuildXL.FrontEnd.Script.Ambients.Transformers
|
|||
private SymbolAtom m_semaphoreInfoLimit;
|
||||
private SymbolAtom m_semaphoreInfoName;
|
||||
private SymbolAtom m_semaphoreInfoIncrementBy;
|
||||
// Explicitly not exposed in DScript since bypassing the salts is not officially supported, and only used for internally scheduled pips
|
||||
private SymbolAtom m_unsafeBypassFingerprintSalt;
|
||||
|
||||
private CallSignature ExecuteSignature => CreateSignature(
|
||||
required: RequiredParameters(AmbientTypes.ExecuteArgumentsType),
|
||||
|
@ -334,7 +332,6 @@ namespace BuildXL.FrontEnd.Script.Ambients.Transformers
|
|||
m_unsafeTrustStaticallyDeclaredAccesses = Symbol("trustStaticallyDeclaredAccesses");
|
||||
m_unsafeDisableFullReparsePointResolving = Symbol("disableFullReparsePointResolving");
|
||||
m_unsafeDisableSandboxing = Symbol("disableSandboxing");
|
||||
m_unsafeBypassFingerprintSalt = Symbol("bypassFingerprintSalt");
|
||||
|
||||
// Semaphore info.
|
||||
m_semaphoreInfoLimit = Symbol("limit");
|
||||
|
@ -1419,12 +1416,6 @@ namespace BuildXL.FrontEnd.Script.Ambients.Transformers
|
|||
{
|
||||
processBuilder.Options |= Process.Options.DisableSandboxing;
|
||||
}
|
||||
|
||||
// UnsafeExecuteArguments.bypassFingerprintSalt
|
||||
if (Converter.ExtractOptionalBoolean(unsafeOptionsObjLit, m_unsafeBypassFingerprintSalt) == true)
|
||||
{
|
||||
processBuilder.Options |= Process.Options.BypassFingerprintSalt;
|
||||
}
|
||||
}
|
||||
|
||||
private PipId InterpretFinalizationPipArguments(Context context, ObjectLiteral obj)
|
||||
|
|
|
@ -222,11 +222,6 @@ namespace BuildXL.FrontEnd.Sdk
|
|||
return EnumerateEntries(path, pattern, recursive, directories: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Translates an absolute path according to the defined translations
|
||||
/// </summary>
|
||||
public abstract AbsolutePath Translate(AbsolutePath path);
|
||||
|
||||
/// <summary>
|
||||
/// A simple IO based file system entry enumeration helper
|
||||
/// </summary>
|
||||
|
|
|
@ -121,7 +121,6 @@ namespace BuildXL.FrontEnd.Sdk
|
|||
string weakPackageFingerprint,
|
||||
PackageIdentity package,
|
||||
AbsolutePath packageTargetFolder,
|
||||
AbsolutePath pathToNuspec,
|
||||
Func<Task<Possible<IReadOnlyList<RelativePath>>>> producePackage);
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -65,12 +65,6 @@ namespace BuildXL.FrontEnd.Sdk
|
|||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AbsolutePath Translate(AbsolutePath path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This method is not implemented.
|
||||
/// </summary>
|
||||
|
@ -92,7 +86,7 @@ namespace BuildXL.FrontEnd.Sdk
|
|||
return false;
|
||||
}
|
||||
|
||||
stream = m_fileSystem.OpenText(AbsolutePath.Create(m_pathTable, physicalPath)).BaseStream;
|
||||
stream = m_fileSystem.OpenText(AbsolutePath.Create(m_pathTable, physicalPath)).BaseStream;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -42,12 +42,6 @@ namespace BuildXL.FrontEnd.Script.Testing.Helper
|
|||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override AbsolutePath Translate(AbsolutePath path)
|
||||
{
|
||||
return path;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool TryGetFrontEndFile(AbsolutePath path, string frontEnd, out Stream stream)
|
||||
{
|
||||
|
|
|
@ -30,7 +30,6 @@ using BuildXL.FrontEnd.Workspaces.Core;
|
|||
using BuildXL.Native.IO;
|
||||
using BuildXL.Pips.Filter;
|
||||
using BuildXL.Utilities;
|
||||
using BuildXL.Utilities.Collections;
|
||||
using BuildXL.Utilities.Configuration;
|
||||
using BuildXL.Utilities.Configuration.Mutable;
|
||||
using BuildXL.Utilities.Instrumentation.Common;
|
||||
|
@ -1075,8 +1074,6 @@ namespace Test.BuildXL.FrontEnd.Core
|
|||
frontEndFactory.InitializeFrontEnds(controller, FrontEndContext, engine.Configuration);
|
||||
|
||||
var frontEndEngineAbstraction = new BasicFrontEndEngineAbstraction(FrontEndContext.PathTable, engineContext.FileSystem, engine.Configuration);
|
||||
frontEndEngineAbstraction.TryPopulateWithDefaultMountsTable(LoggingContext, engineContext, engine.Configuration, CollectionUtilities.EmptyDictionary<string, string>());
|
||||
|
||||
controller.SetState(frontEndEngineAbstraction, GetPipGraph(), config);
|
||||
|
||||
var evaluationFilter = fileToProcess.IsValid ? EvaluationFilter.FromSingleSpecPath(FrontEndContext.SymbolTable, FrontEndContext.PathTable, fileToProcess) : EvaluationFilter.Empty;
|
||||
|
|
|
@ -98,7 +98,7 @@ namespace Context {
|
|||
*/
|
||||
interface CurrentHostInformation {
|
||||
/** The current Os type */
|
||||
os: "win" | "macOS" | "unix";
|
||||
os: "win" | "mac" | "unix";
|
||||
/** The current cpu architecture */
|
||||
cpuArchitecture: "x64" | "x86";
|
||||
/** Wheter the current build is run with elevated permissions (admin/sudo) */
|
||||
|
|
|
@ -11,18 +11,17 @@ using Xunit;
|
|||
using Xunit.Abstractions;
|
||||
using Test.BuildXL.TestUtilities.Xunit;
|
||||
using System;
|
||||
using BuildXL.Utilities;
|
||||
|
||||
namespace Test.BuildXL.FrontEnd.Nuget
|
||||
{
|
||||
public class NuSpecGeneratorTests
|
||||
{
|
||||
private const int CurrentSpecGenVersion = 12;
|
||||
private const int CurrentSpecGenVersion = 11;
|
||||
|
||||
private readonly ITestOutputHelper m_output;
|
||||
private readonly FrontEndContext m_context;
|
||||
private readonly PackageGenerator m_packageGenerator;
|
||||
private readonly Dictionary<string, string> m_repositories;
|
||||
|
||||
private static readonly INugetPackage s_myPackage = new NugetPackage() { Id = "MyPkg", Version = "1.99" };
|
||||
private static readonly INugetPackage s_systemCollections = new NugetPackage() { Id = "System.Collections", Version = "4.0.11" };
|
||||
private static readonly INugetPackage s_systemCollectionsConcurrent = new NugetPackage() { Id = "System.Collections.Concurrent", Version = "4.0.12" };
|
||||
|
@ -43,8 +42,6 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
|
||||
var monikers = new NugetFrameworkMonikers(m_context.StringTable);
|
||||
m_packageGenerator = new PackageGenerator(m_context, monikers);
|
||||
|
||||
m_repositories = new Dictionary<string, string>() { ["BuildXL"] = "https://pkgs.dev.azure.com/cloudbuild/_packaging/BuildXL.Selfhost/nuget/v3/index.json" };
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -73,11 +70,11 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
</package>",
|
||||
s_packagesOnConfig, new string[] { "lib/net45/my.dll", "lib/net451/my.dll", "lib/netstandard2.0/my.dll"});
|
||||
|
||||
var spec = new NugetSpecGenerator(m_context.PathTable, pkg, m_repositories, AbsolutePath.Invalid).CreateScriptSourceFile(pkg);
|
||||
var spec = new NugetSpecGenerator(m_context.PathTable, pkg).CreateScriptSourceFile(pkg);
|
||||
var text = spec.ToDisplayStringV2();
|
||||
m_output.WriteLine(text);
|
||||
|
||||
string expectedSpec = $@"import * as NugetDownloader from ""BuildXL.Tools.NugetDownloader"";
|
||||
string expectedSpec = $@"import {{Transformer}} from ""Sdk.Transformers"";
|
||||
import * as Managed from ""Sdk.Managed"";
|
||||
|
||||
export declare const qualifier: {{
|
||||
|
@ -85,23 +82,22 @@ export declare const qualifier: {{
|
|||
targetRuntime: ""win-x64"" | ""osx-x64"" | ""linux-x64"",
|
||||
}};
|
||||
|
||||
const packageRoot = Contents.packageRoot;
|
||||
|
||||
namespace Contents {{
|
||||
export declare const qualifier: {{
|
||||
}};
|
||||
const outputDir: Directory = Context.getNewOutputDirectory(""nuget"");
|
||||
export const packageRoot = d`../../../pkgs/TestPkg.1.999`;
|
||||
@@public
|
||||
export const all: StaticDirectory = NugetDownloader.downloadPackage({{
|
||||
id: ""TestPkg"",
|
||||
version: ""1.999"",
|
||||
downloadDirectory: outputDir,
|
||||
extractedFiles: [
|
||||
r`TestPkg.nuspec`,
|
||||
r`lib/net45/my.dll`,
|
||||
r`lib/net451/my.dll`,
|
||||
r`lib/netstandard2.0/my.dll`,
|
||||
],
|
||||
repositories: [[""BuildXL"", ""https://pkgs.dev.azure.com/cloudbuild/_packaging/BuildXL.Selfhost/nuget/v3/index.json""]],
|
||||
}});
|
||||
export const all: StaticDirectory = Transformer.sealDirectory(
|
||||
packageRoot,
|
||||
[
|
||||
f`${{packageRoot}}/lib/net45/my.dll`,
|
||||
f`${{packageRoot}}/lib/net451/my.dll`,
|
||||
f`${{packageRoot}}/lib/netstandard2.0/my.dll`,
|
||||
f`${{packageRoot}}/TestPkg.nuspec`,
|
||||
]
|
||||
);
|
||||
}}
|
||||
|
||||
@@public
|
||||
|
@ -112,12 +108,9 @@ export const pkg: Managed.ManagedNugetPackage = (() => {{
|
|||
""TestPkg"",
|
||||
""1.999"",
|
||||
Contents.all,
|
||||
[Managed.Factory.createBinaryFromFiles(Contents.all.getFile(r`lib/net45/my.dll`))],
|
||||
[Managed.Factory.createBinaryFromFiles(Contents.all.getFile(r`lib/net45/my.dll`))],
|
||||
[
|
||||
importFrom(""System.Collections"").pkg,
|
||||
importFrom(""System.Collections.Concurrent"").pkg,
|
||||
]
|
||||
[Managed.Factory.createBinaryFromFiles(f`${{packageRoot}}/lib/net45/my.dll`)],
|
||||
[Managed.Factory.createBinaryFromFiles(f`${{packageRoot}}/lib/net45/my.dll`)],
|
||||
[importFrom(""System.Collections"").pkg, importFrom(""System.Collections.Concurrent"").pkg]
|
||||
);
|
||||
case ""net451"":
|
||||
case ""net452"":
|
||||
|
@ -129,12 +122,9 @@ export const pkg: Managed.ManagedNugetPackage = (() => {{
|
|||
""TestPkg"",
|
||||
""1.999"",
|
||||
Contents.all,
|
||||
[Managed.Factory.createBinaryFromFiles(Contents.all.getFile(r`lib/net451/my.dll`))],
|
||||
[Managed.Factory.createBinaryFromFiles(Contents.all.getFile(r`lib/net451/my.dll`))],
|
||||
[
|
||||
importFrom(""System.Collections"").pkg,
|
||||
importFrom(""System.Collections.Concurrent"").pkg,
|
||||
]
|
||||
[Managed.Factory.createBinaryFromFiles(f`${{packageRoot}}/lib/net451/my.dll`)],
|
||||
[Managed.Factory.createBinaryFromFiles(f`${{packageRoot}}/lib/net451/my.dll`)],
|
||||
[importFrom(""System.Collections"").pkg, importFrom(""System.Collections.Concurrent"").pkg]
|
||||
);
|
||||
case ""netstandard2.0"":
|
||||
case ""netcoreapp2.0"":
|
||||
|
@ -149,9 +139,9 @@ export const pkg: Managed.ManagedNugetPackage = (() => {{
|
|||
""TestPkg"",
|
||||
""1.999"",
|
||||
Contents.all,
|
||||
[Managed.Factory.createBinaryFromFiles(Contents.all.getFile(r`lib/netstandard2.0/my.dll`))],
|
||||
[Managed.Factory.createBinaryFromFiles(f`${{packageRoot}}/lib/netstandard2.0/my.dll`)],
|
||||
[
|
||||
Managed.Factory.createBinaryFromFiles(Contents.all.getFile(r`lib/netstandard2.0/my.dll`)),
|
||||
Managed.Factory.createBinaryFromFiles(f`${{packageRoot}}/lib/netstandard2.0/my.dll`),
|
||||
],
|
||||
[...addIfLazy(qualifier.targetFramework === ""netstandard2.0"", () => [importFrom(""Newtonsoft.Json"").pkg])]
|
||||
);
|
||||
|
@ -160,9 +150,9 @@ export const pkg: Managed.ManagedNugetPackage = (() => {{
|
|||
}};
|
||||
}}
|
||||
)();";
|
||||
XAssert.AreEqual(expectedSpec.Trim(), text.Trim());
|
||||
XAssert.AreEqual(expectedSpec, text);
|
||||
|
||||
const string CurrentSpecHash = "1291F51E8E59ED9C2F967C01D49CC39FE6DEB9D8";
|
||||
const string CurrentSpecHash = "FDBA16F722AB64B5C61F4CBAD40500C9B1944E39";
|
||||
ValidateCurrentSpecGenVersion(expectedSpec, CurrentSpecHash);
|
||||
}
|
||||
|
||||
|
@ -170,29 +160,25 @@ export const pkg: Managed.ManagedNugetPackage = (() => {{
|
|||
public void GenerateNuSpecForStub()
|
||||
{
|
||||
var pkg = m_packageGenerator.AnalyzePackageStub(s_packagesOnConfig);
|
||||
var spec = new NugetSpecGenerator(m_context.PathTable, pkg, m_repositories, AbsolutePath.Invalid).CreateScriptSourceFile(pkg);
|
||||
var spec = new NugetSpecGenerator(m_context.PathTable, pkg).CreateScriptSourceFile(pkg);
|
||||
var text = spec.ToDisplayStringV2();
|
||||
m_output.WriteLine(text);
|
||||
|
||||
string expectedSpec = @"import * as NugetDownloader from ""BuildXL.Tools.NugetDownloader"";
|
||||
string expectedSpec = @"import {Transformer} from ""Sdk.Transformers"";
|
||||
|
||||
export declare const qualifier: {
|
||||
targetFramework: ""net10"" | ""net11"" | ""net20"" | ""net35"" | ""net40"" | ""net45"" | ""net451"" | ""net452"" | ""net46"" | ""net461"" | ""net462"" | ""net472"" | ""netstandard1.0"" | ""netstandard1.1"" | ""netstandard1.2"" | ""netstandard1.3"" | ""netstandard1.4"" | ""netstandard1.5"" | ""netstandard1.6"" | ""netstandard2.0"" | ""netcoreapp2.0"" | ""netcoreapp2.1"" | ""netcoreapp2.2"" | ""netcoreapp3.0"" | ""netcoreapp3.1"" | ""net5.0"" | ""net6.0"" | ""netstandard2.1"",
|
||||
targetRuntime: ""win-x64"" | ""osx-x64"" | ""linux-x64"",
|
||||
};
|
||||
|
||||
const packageRoot = Contents.packageRoot;
|
||||
|
||||
namespace Contents {
|
||||
export declare const qualifier: {
|
||||
};
|
||||
const outputDir: Directory = Context.getNewOutputDirectory(""nuget"");
|
||||
export const packageRoot = d`../../../pkgs/TestPkgStub.1.999`;
|
||||
@@public
|
||||
export const all: StaticDirectory = NugetDownloader.downloadPackage({
|
||||
id: ""TestPkgStub"",
|
||||
version: ""1.999"",
|
||||
downloadDirectory: outputDir,
|
||||
extractedFiles: [],
|
||||
repositories: [[""BuildXL"", ""https://pkgs.dev.azure.com/cloudbuild/_packaging/BuildXL.Selfhost/nuget/v3/index.json""]],
|
||||
});
|
||||
export const all: StaticDirectory = Transformer.sealDirectory(packageRoot, []);
|
||||
}
|
||||
|
||||
@@public
|
||||
|
@ -203,7 +189,7 @@ export const pkg: NugetPackage = {
|
|||
};";
|
||||
XAssert.ArrayEqual(SplitToLines(expectedSpec), SplitToLines(text));
|
||||
|
||||
const string CurrentSpecHash = "389BC336BD4B206394E01B1E76A859E6561CE30D";
|
||||
const string CurrentSpecHash = "8550BF2B35888E3C0095095F44E2EB06FC2F0576";
|
||||
ValidateCurrentSpecGenVersion(expectedSpec, CurrentSpecHash);
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
var nugetResolver = CreateWorkspaceNugetResolverForTesting();
|
||||
|
||||
var allPackages = new Dictionary<string, INugetPackage> { [packageOnDisk.Package.Id] = packageOnDisk.Package };
|
||||
var analyzedPackage = nugetResolver.AnalyzeNugetPackage(packageOnDisk, AbsolutePath.Invalid, false);
|
||||
var analyzedPackage = nugetResolver.AnalyzeNugetPackage(packageOnDisk, false);
|
||||
|
||||
XAssert.IsTrue(analyzedPackage.Succeeded);
|
||||
|
||||
|
@ -86,7 +86,7 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
|
||||
var allPackages = new Dictionary<string, INugetPackage> { [packageOnDisk.Package.Id] = packageOnDisk.Package };
|
||||
|
||||
var analyzedPackage = nugetResolver.AnalyzeNugetPackage(packageOnDisk, AbsolutePath.Invalid, false);
|
||||
var analyzedPackage = nugetResolver.AnalyzeNugetPackage(packageOnDisk, false);
|
||||
|
||||
XAssert.IsTrue(analyzedPackage.Succeeded);
|
||||
|
||||
|
@ -105,10 +105,10 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
var packageText = File.ReadAllText(packageDsc);
|
||||
XAssert.IsFalse(packageText.Contains("export * from"));
|
||||
XAssert.IsFalse(packageText.Contains("/Foo.Bar.dsc\""));
|
||||
XAssert.IsTrue(packageText.Contains(@"id: ""Foo.Bar"""));
|
||||
XAssert.IsTrue(packageText.Contains(@"version: ""1.2"""));
|
||||
XAssert.IsTrue(packageText.Contains("r`Folder/a.txt`"));
|
||||
XAssert.IsTrue(packageText.Contains("r`Folder/b.txt`"));
|
||||
XAssert.IsTrue(packageText.Contains("/pkgs/Foo.Bar.1.2`"));
|
||||
XAssert.IsTrue(packageText.Contains("packageRoot = d`"));
|
||||
XAssert.IsTrue(packageText.Contains("f`${packageRoot}/Folder/a.txt`"));
|
||||
XAssert.IsTrue(packageText.Contains("f`${packageRoot}/Folder/b.txt`"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -140,7 +140,7 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
var pathTable = m_testContext.PathTable;
|
||||
var packageOnDisk = CreateTestPackageOnDisk(includeScriptSpec: false, version: version);
|
||||
var nugetResolver = CreateWorkspaceNugetResolverForTesting();
|
||||
var analyzedPackage = nugetResolver.AnalyzeNugetPackage(packageOnDisk, AbsolutePath.Invalid, false);
|
||||
var analyzedPackage = nugetResolver.AnalyzeNugetPackage(packageOnDisk, false);
|
||||
XAssert.IsTrue(analyzedPackage.Succeeded);
|
||||
var allPackages = new Dictionary<string, NugetAnalyzedPackage> { [packageOnDisk.Package.Id] = analyzedPackage.Result };
|
||||
XAssert.IsTrue(analyzedPackage.Succeeded);
|
||||
|
@ -314,7 +314,7 @@ $@"module({{
|
|||
{
|
||||
var allPackages = packagesOnDisk.ToDictionary(kvp => kvp.Package.Id, kvp => kvp.Package);
|
||||
|
||||
var allAnalyzedPackages = packagesOnDisk.ToDictionary(package => package.Package.Id, package => resolver.AnalyzeNugetPackage(package, AbsolutePath.Invalid, false).Result);
|
||||
var allAnalyzedPackages = packagesOnDisk.ToDictionary(package => package.Package.Id, package => resolver.AnalyzeNugetPackage(package, false).Result);
|
||||
|
||||
resolver.SetDownloadedPackagesForTesting(allAnalyzedPackages);
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
paths,
|
||||
"testPackageHash"));
|
||||
|
||||
return NugetAnalyzedPackage.TryAnalyzeNugetPackage(m_context, m_monikers, XDocument.Parse(xml), packageOnDisk, packagesOnConfig, false, AbsolutePath.Invalid);
|
||||
return NugetAnalyzedPackage.TryAnalyzeNugetPackage(m_context, m_monikers, XDocument.Parse(xml), packageOnDisk, packagesOnConfig, false);
|
||||
}
|
||||
|
||||
public NugetAnalyzedPackage AnalyzePackageStub(Dictionary<string, INugetPackage> packagesOnConfig)
|
||||
|
@ -58,7 +58,7 @@ namespace Test.BuildXL.FrontEnd.Nuget
|
|||
new PackageIdentity("nuget", nugetPackage.Id, nugetPackage.Version, nugetPackage.Alias),
|
||||
packageFolder: AbsolutePath.Create(m_context.PathTable, X("/X/Pkgs/TestPkgStub/1.999"))));
|
||||
|
||||
return NugetAnalyzedPackage.TryAnalyzeNugetPackage(m_context, m_monikers, nuSpec: null, packageOnDisk, packagesOnConfig, false, AbsolutePath.Invalid);
|
||||
return NugetAnalyzedPackage.TryAnalyzeNugetPackage(m_context, m_monikers, nuSpec: null, packageOnDisk, packagesOnConfig, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,18 @@ namespace Test.DScript.Workspaces.Utilities
|
|||
{
|
||||
return new MalformedConfigurationFailure("An array literal is expected for NuGet credential providers");
|
||||
}
|
||||
|
||||
var credentialProviders = expression.Cast<IArrayLiteralExpression>();
|
||||
foreach (var element in credentialProviders.Elements)
|
||||
{
|
||||
var elementResult = ParseNugetConfigurationFrom(element.As<IObjectLiteralExpression>());
|
||||
if (!elementResult.Succeeded)
|
||||
{
|
||||
return elementResult;
|
||||
}
|
||||
|
||||
result.CredentialProviders.Add(elementResult.Result);
|
||||
}
|
||||
}
|
||||
|
||||
if (configurationExpression.TryFindAssignmentPropertyInitializer(NaiveConfigurationParsingUtilities.ToolUrlFieldName, out expression))
|
||||
|
|
|
@ -145,12 +145,11 @@ namespace BuildXL.Ide.LanguageServer.UnitTests
|
|||
.OrderBy(ci => ci.Label)
|
||||
.ToArray();
|
||||
|
||||
Assert.Equal(2, completionItems.Length);
|
||||
Assert.Equal(1, completionItems.Length);
|
||||
|
||||
// TODO: Change to just BuildXL.DScript check after April 15, 2019 when deployed bits have updated.
|
||||
Assert.True(completionItems[0].Label == "BuildXL.DScript.LanguageServer.UnitTests.Data.Module" ||
|
||||
completionItems[0].Label == "BuildXLScript.LanguageServer.UnitTests.Data.Module");
|
||||
Assert.True(completionItems[1].Label == "BuildXL.Tools.NugetDownloader");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -413,9 +413,9 @@ namespace BuildXL.Pips.Graph
|
|||
/// <summary>
|
||||
/// Add fields to fingerprint
|
||||
/// </summary>
|
||||
public void AddFingerprint(IHashingHelper fingerprinter, bool bypassFingerprintSaltAndVersion)
|
||||
public void AddFingerprint(IHashingHelper fingerprinter)
|
||||
{
|
||||
if (!bypassFingerprintSaltAndVersion && !string.IsNullOrEmpty(FingerprintSalt))
|
||||
if (!string.IsNullOrEmpty(FingerprintSalt))
|
||||
{
|
||||
fingerprinter.Add(nameof(FingerprintSalt), FingerprintSalt);
|
||||
}
|
||||
|
@ -455,17 +455,14 @@ namespace BuildXL.Pips.Graph
|
|||
fingerprinter.Add(nameof(ExplicitlyReportDirectoryProbes), 1);
|
||||
}
|
||||
|
||||
if (!bypassFingerprintSaltAndVersion)
|
||||
{
|
||||
fingerprinter.Add(nameof(FingerprintVersion), (int)FingerprintVersion);
|
||||
}
|
||||
fingerprinter.Add(nameof(FingerprintVersion), (int)FingerprintVersion);
|
||||
}
|
||||
|
||||
private CalculatedFingerprintTuple ComputeWeakFingerprint()
|
||||
{
|
||||
using (var hasher = new CoreHashingHelper(true))
|
||||
{
|
||||
AddFingerprint(hasher, bypassFingerprintSaltAndVersion: false);
|
||||
AddFingerprint(hasher);
|
||||
return new CalculatedFingerprintTuple(hasher.GenerateHash(), hasher.FingerprintInputText);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ namespace BuildXL.Pips.Graph
|
|||
Contract.Requires(fingerprinter != null);
|
||||
Contract.Requires(pip != null);
|
||||
|
||||
fingerprinter.AddNested(PipFingerprintField.ExecutionAndFingerprintOptions, fp => m_extraFingerprintSalts.AddFingerprint(fp, pip.BypassFingerprintSalt));
|
||||
fingerprinter.AddNested(PipFingerprintField.ExecutionAndFingerprintOptions, fp => m_extraFingerprintSalts.AddFingerprint(fp));
|
||||
|
||||
// Fingerprints must change when outputs are hashed with a different algorithm.
|
||||
fingerprinter.Add(PipFingerprintField.ContentHashAlgorithmName, s_outputContentHashAlgorithmName);
|
||||
|
|
|
@ -70,11 +70,6 @@ namespace BuildXL.Pips.Operations
|
|||
[PipCaching(FingerprintingRole = FingerprintingRole.None)]
|
||||
public abstract PipType PipType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// When set, the pip fingerprint is not sensitive to fingerprint salts. This excludes both EngineEnvironmentSettings.DebugFingerprintSalt and PipFingerprintingVersion.TwoPhaseV2
|
||||
/// </summary>
|
||||
public virtual bool BypassFingerprintSalt => false;
|
||||
|
||||
/// <nodoc />
|
||||
internal Pip()
|
||||
{
|
||||
|
|
|
@ -182,11 +182,6 @@ namespace BuildXL.Pips.Operations
|
|||
/// Whether to disable sandboxing for this process
|
||||
/// </summary>
|
||||
DisableSandboxing = 1 << 21,
|
||||
|
||||
/// <summary>
|
||||
/// When set, the pip fingerprint is not sensitive to fingerprint salts. This excludes both <see cref="EngineEnvironmentSettings.DebugFingerprintSalt"/> and PipFingerprintingVersion.TwoPhaseV2
|
||||
/// </summary>
|
||||
BypassFingerprintSalt = 1 << 22,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -842,10 +842,6 @@ namespace BuildXL.Pips.Operations
|
|||
/// </summary>
|
||||
public bool HasSharedOpaqueDirectoryOutputs => DirectoryOutputs.Any(d => d.IsSharedOpaque);
|
||||
|
||||
/// <inheritdoc/>
|
||||
[PipCaching(FingerprintingRole = FingerprintingRole.Semantic)]
|
||||
public override bool BypassFingerprintSalt => (ProcessOptions & Options.BypassFingerprintSalt) != 0;
|
||||
|
||||
/// <summary>
|
||||
/// What policy to apply when merging redirected outputs back
|
||||
/// </summary>
|
||||
|
|
|
@ -7,6 +7,8 @@ import * as SdkDeployment from "Sdk.Deployment";
|
|||
namespace Deployment {
|
||||
export declare const qualifier: {configuration: "debug" | "release"};
|
||||
|
||||
const pkgPath = d`${importFrom("runtime.osx-x64.BuildXL").Contents.all.root}/runtimes/osx-x64/native`;
|
||||
|
||||
@@public
|
||||
export const macBinaryUsage = Context.getCurrentHost().os === "macOS"
|
||||
? (BuildXLSdk.Flags.isValidatingOsxRuntime ? "package" : "build")
|
||||
|
@ -20,9 +22,7 @@ namespace Deployment {
|
|||
contents: macBinaryUsage === "none"
|
||||
? []
|
||||
: macBinaryUsage === "package"
|
||||
? [ SdkDeployment.createFromFilteredStaticDirectory(
|
||||
importFrom("runtime.osx-x64.BuildXL").Contents.all.ensureContents({subFolder: r`runtimes/osx-x64/native/${qualifier.configuration}/BuildXLSandbox.kext`}),
|
||||
r`.`)]
|
||||
? [ SdkDeployment.createFromDisk(d`${pkgPath}/${qualifier.configuration}/BuildXLSandbox.kext`) ]
|
||||
: [{
|
||||
subfolder: a`Contents`,
|
||||
contents: [
|
||||
|
@ -47,9 +47,7 @@ namespace Deployment {
|
|||
contents: macBinaryUsage === "none"
|
||||
? []
|
||||
: macBinaryUsage === "package"
|
||||
? [ SdkDeployment.createFromFilteredStaticDirectory(
|
||||
importFrom("runtime.osx-x64.BuildXL").Contents.all.ensureContents({subFolder: r`runtimes/osx-x64/native/${qualifier.configuration}/BuildXLSandbox.kext.dSYM`}),
|
||||
r`.`)]
|
||||
? [ SdkDeployment.createFromDisk(d`${pkgPath}/${qualifier.configuration}/BuildXLSandbox.kext.dSYM`) ]
|
||||
: [{
|
||||
subfolder: a`Contents`,
|
||||
contents: [
|
||||
|
@ -85,34 +83,30 @@ namespace Deployment {
|
|||
|
||||
@@public
|
||||
export const sandboxMonitor: SdkDeployment.Definition = {
|
||||
contents:
|
||||
contents: [
|
||||
macBinaryUsage === "build"
|
||||
? [Sandbox.monitor]
|
||||
: macBinaryUsage === "package"
|
||||
? [importFrom("runtime.osx-x64.BuildXL").Contents.all.getFile(r`runtimes/osx-x64/native/${qualifier.configuration}/SandboxMonitor`)]
|
||||
: []
|
||||
? Sandbox.monitor
|
||||
: f`${pkgPath}/${qualifier.configuration}/SandboxMonitor`
|
||||
]
|
||||
};
|
||||
|
||||
@@public
|
||||
export const interopLibrary: SdkDeployment.Definition = {
|
||||
contents: macBinaryUsage === "build"
|
||||
? [ Sandbox.libInterop, Sandbox.libDetours ]
|
||||
: macBinaryUsage === "package"
|
||||
? [
|
||||
importFrom("runtime.osx-x64.BuildXL").Contents.all.getFile(r`runtimes/osx-x64/native/${qualifier.configuration}/libBuildXLInterop.dylib`),
|
||||
importFrom("runtime.osx-x64.BuildXL").Contents.all.getFile(r`runtimes/osx-x64/native/${qualifier.configuration}/libBuildXLDetours.dylib`)
|
||||
]
|
||||
: []
|
||||
: [
|
||||
f`${pkgPath}/${qualifier.configuration}/libBuildXLInterop.dylib`,
|
||||
f`${pkgPath}/${qualifier.configuration}/libBuildXLDetours.dylib`
|
||||
]
|
||||
};
|
||||
|
||||
@@public
|
||||
export const coreDumpTester: SdkDeployment.Definition = {
|
||||
contents:
|
||||
contents: [
|
||||
macBinaryUsage === "build"
|
||||
? [Sandbox.coreDumpTester]
|
||||
: macBinaryUsage === "package"
|
||||
? [importFrom("runtime.osx-x64.BuildXL").Contents.all.getFile(r`runtimes/osx-x64/native/${qualifier.configuration}/CoreDumpTester`)]
|
||||
: []
|
||||
? Sandbox.coreDumpTester
|
||||
: f`${pkgPath}/${qualifier.configuration}/CoreDumpTester`
|
||||
]
|
||||
};
|
||||
|
||||
@@public
|
||||
|
|
|
@ -218,7 +218,7 @@ namespace BuildXL.FrontEnd.Script.Analyzer
|
|||
Contract.AssertNotNull(frontEndEngineAbstraction);
|
||||
|
||||
AddConfigurationMounts(config, mountsTable);
|
||||
languageServiceEngine.UpdateMountsTable(mountsTable);
|
||||
languageServiceEngine.SetMountsTable(mountsTable);
|
||||
}
|
||||
|
||||
if (frontEndEngineAbstraction == null)
|
||||
|
|
|
@ -2,19 +2,21 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildXL.Cache.ContentStore.Hashing;
|
||||
using BuildXL.Native.IO;
|
||||
using BuildXL.Storage;
|
||||
using BuildXL.ToolSupport;
|
||||
using BuildXL.Utilities;
|
||||
using BuildXL.Utilities.VstsAuthentication;
|
||||
using Microsoft.IdentityModel.Clients.ActiveDirectory;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Tool.Download
|
||||
{
|
||||
|
@ -92,15 +94,11 @@ namespace Tool.Download
|
|||
|
||||
var httpRequest = new HttpRequestMessage(HttpMethod.Get, arguments.Url);
|
||||
|
||||
var logger = new StringBuilder();
|
||||
|
||||
// If the download URI is pointing to a VSTS feed and we get a valid auth token, make it part of the request
|
||||
// We only want to send the token over HTTPS and to a VSTS domain to avoid security issues
|
||||
if (VSTSAuthenticationHelper.IsVSTSPackageSecureURI(arguments.Url) &&
|
||||
await VSTSAuthenticationHelper.IsAuthenticationRequiredAsync(arguments.Url, CancellationToken.None, logger) &&
|
||||
await VSTSAuthenticationHelper.TryGetAuthenticationCredentialsAsync(arguments.Url, CancellationToken.None) is var maybeAuthCredentials &&
|
||||
maybeAuthCredentials.Succeeded &&
|
||||
VSTSAuthenticationHelper.GetAuthenticationHeaderFromPAT(maybeAuthCredentials.Result.pat) is var authHeader)
|
||||
if (IsVSTSPackageSecureURI(arguments.Url) &&
|
||||
await TryGetAuthenticationHeaderAsync(arguments.Url) is var authHeader &&
|
||||
authHeader != null)
|
||||
{
|
||||
httpRequest.Headers.Accept.Clear();
|
||||
httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
|
@ -164,5 +162,175 @@ namespace Tool.Download
|
|||
return await ContentHashingUtilities.HashContentStreamAsync(fs, hashType);
|
||||
|
||||
}
|
||||
|
||||
private bool IsVSTSPackageSecureURI(Uri downloadURI)
|
||||
{
|
||||
return downloadURI.Scheme == "https" &&
|
||||
(downloadURI.Host.EndsWith(".pkgs.visualstudio.com", StringComparison.OrdinalIgnoreCase) || downloadURI.Host.EndsWith(".pkgs.dev.azure.com", StringComparison.OrdinalIgnoreCase)) ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to authenticate using a credential provider and fallback to IWA if that fails.
|
||||
/// </summary>
|
||||
private async Task<AuthenticationHeaderValue> TryGetAuthenticationHeaderAsync(Uri uri)
|
||||
{
|
||||
var result = await TryGetAuthenticationHeaderValueWithCredentialProviderAsync(uri);
|
||||
if (result != null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return await TryGetAuthenticationHeaderWithIWAAsync(uri);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get an authentication token using Integrated Windows Authentication with the current logged in user
|
||||
/// </summary>
|
||||
/// <returns>Null if authentication fails</returns>
|
||||
/// <remarks>
|
||||
/// The auth token is acquired to address simple auth cases for retrieving packages from VSTS feeds from Windows domain
|
||||
/// joined machines where IWA is enabled.
|
||||
/// See https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Integrated-Windows-Authentication
|
||||
/// </remarks>
|
||||
private async Task<AuthenticationHeaderValue> TryGetAuthenticationHeaderWithIWAAsync(Uri uri)
|
||||
{
|
||||
var authenticationContext = new AuthenticationContext(m_authority);
|
||||
|
||||
try
|
||||
{
|
||||
var userCredential = new UserCredential($"{Environment.UserName}@{Tenant}");
|
||||
|
||||
// Many times the user UPN cannot be automatically retrieved, so we build it based on the current username and tenant. This might
|
||||
// not be perfect but should work for most cases. Getting the UPN for a given user from AD requires authentication as well.
|
||||
var result = await authenticationContext.AcquireTokenAsync(Resource, Client, userCredential); ;
|
||||
return new AuthenticationHeaderValue("Bearer", result.AccessToken);
|
||||
}
|
||||
catch (AdalException ex)
|
||||
{
|
||||
Console.WriteLine($"Download resolver was not able to authenticate using Integrated Windows Authentication for '{uri}': {ex.Message}");
|
||||
// Getting an auth token via IWA is on a best effort basis. If anything fails, we silently continue without auth
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get an authentication token using a credential provider
|
||||
/// </summary>
|
||||
/// <returns>Null if authentication fails</returns>
|
||||
/// <remarks>
|
||||
/// The credential provider is discovered following the definition here: https://docs.microsoft.com/en-us/nuget/reference/extensibility/nuget-exe-credential-providers
|
||||
/// using an environment variable.
|
||||
/// Observe the link above is for nuget specifically, but the authentication here is used for VSTS feeds in general
|
||||
/// </remarks>
|
||||
private async Task<AuthenticationHeaderValue> TryGetAuthenticationHeaderValueWithCredentialProviderAsync(Uri uri)
|
||||
{
|
||||
string credentialProviderPath = DiscoverCredentialProvider(uri);
|
||||
|
||||
if (credentialProviderPath == null)
|
||||
{
|
||||
// Failure has been logged already
|
||||
return null;
|
||||
}
|
||||
|
||||
// Call the provider with the requested URI in non-interactive mode.
|
||||
var processInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = credentialProviderPath,
|
||||
Arguments = $"-Uri {uri.AbsoluteUri} -NonInteractive",
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
|
||||
using (var process = Process.Start(processInfo))
|
||||
{
|
||||
try
|
||||
{
|
||||
#pragma warning disable AsyncFixer02 // WaitForExitAsync should be used instead
|
||||
process?.WaitForExit();
|
||||
#pragma warning restore AsyncFixer02
|
||||
}
|
||||
catch (Exception e) when (e is SystemException || e is Win32Exception)
|
||||
{
|
||||
ReportAuthenticationViaCredentialProviderFailed(uri, $"Credential provider execution '{credentialProviderPath}' failed. Error: {e.Message}");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (process == null)
|
||||
{
|
||||
// The process was not started
|
||||
ReportAuthenticationViaCredentialProviderFailed(uri, $"Could not start credential provider process '{credentialProviderPath}'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check whether the authorization succeeded
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
// The response should be a well-formed JSON with a 'password' entry representing the PAT
|
||||
var response = (JObject)await JToken.ReadFromAsync(new JsonTextReader(process.StandardOutput));
|
||||
var pat = response.GetValue("Password");
|
||||
|
||||
if (pat == null)
|
||||
{
|
||||
ReportAuthenticationViaCredentialProviderFailed(uri, $"Could not find a 'password' entry in the JSON response of '{credentialProviderPath}'.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// We got a PAT back. Create an authentication header with a base64 encoding of the retrieved PAT
|
||||
return new AuthenticationHeaderValue("Basic",
|
||||
Convert.ToBase64String(
|
||||
System.Text.ASCIIEncoding.ASCII.GetBytes(
|
||||
string.Format("{0}:{1}", "", pat))));
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{
|
||||
ReportAuthenticationViaCredentialProviderFailed(uri, $"The credential provider '{credentialProviderPath}' response is not a well-formed JSON.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
ReportAuthenticationViaCredentialProviderFailed(uri, $"The credential provider '{credentialProviderPath}' returned a non-succesful exit code: {process.ExitCode}.");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private string DiscoverCredentialProvider(Uri uri)
|
||||
{
|
||||
var paths = Environment.GetEnvironmentVariable("NUGET_CREDENTIALPROVIDERS_PATH");
|
||||
|
||||
if (paths == null)
|
||||
{
|
||||
ReportAuthenticationViaCredentialProviderFailed(uri, $"NUGET_CREDENTIALPROVIDERS_PATH is not set.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// Here we do something slightly simpler than what NuGet does and just look for the first credential
|
||||
// provider we can find
|
||||
string credentialProviderPath = null;
|
||||
foreach (string path in paths.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
credentialProviderPath = Directory.EnumerateFiles(path, "credentialprovider*.exe", SearchOption.TopDirectoryOnly).FirstOrDefault();
|
||||
if (credentialProviderPath != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (credentialProviderPath == null)
|
||||
{
|
||||
ReportAuthenticationViaCredentialProviderFailed(uri, $"Credential provider was not found under '{paths}'.");
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return credentialProviderPath;
|
||||
}
|
||||
|
||||
private void ReportAuthenticationViaCredentialProviderFailed(Uri url, string details)
|
||||
{
|
||||
Console.WriteLine($"Download resolver was not able to authenticate using a credential provider for '{url}': {details}") ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ namespace FileDownloader {
|
|||
sources: globR(d`.`, "Downloader*.cs"),
|
||||
references:[
|
||||
importFrom("Newtonsoft.Json").pkg,
|
||||
importFrom("BuildXL.Utilities").VstsAuthentication.dll,
|
||||
importFrom("BuildXL.Utilities").Collections.dll,
|
||||
importFrom("BuildXL.Utilities").Native.dll,
|
||||
importFrom("BuildXL.Utilities.Instrumentation").Common.dll,
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<ThrowUnobservedTaskExceptions enabled="true"/>
|
||||
</runtime>
|
||||
</configuration>
|
|
@ -1,84 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using NuGet.Common;
|
||||
|
||||
namespace NugetDownloader
|
||||
{
|
||||
/// <summary>
|
||||
/// Logs all messages to the console
|
||||
/// </summary>
|
||||
internal class ConsoleLogger : ILogger
|
||||
{
|
||||
/// <nodoc/>
|
||||
public void Log(LogLevel level, string data)
|
||||
{
|
||||
Console.WriteLine($"[{level}]:{data}");
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void Log(ILogMessage message)
|
||||
{
|
||||
Console.WriteLine(message.FormatWithCode());
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public Task LogAsync(LogLevel level, string data)
|
||||
{
|
||||
Log(level, data);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public Task LogAsync(ILogMessage message)
|
||||
{
|
||||
Log(message);
|
||||
return Task.CompletedTask;
|
||||
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void LogDebug(string data)
|
||||
{
|
||||
Log(LogLevel.Debug, data);
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void LogError(string data)
|
||||
{
|
||||
Log(LogLevel.Error, data);
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void LogInformation(string data)
|
||||
{
|
||||
Log(LogLevel.Information, data);
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void LogInformationSummary(string data)
|
||||
{
|
||||
Log(LogLevel.Information, data);
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void LogMinimal(string data)
|
||||
{
|
||||
Log(LogLevel.Information, data);
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void LogVerbose(string data)
|
||||
{
|
||||
Log(LogLevel.Verbose, data);
|
||||
}
|
||||
|
||||
/// <nodoc/>
|
||||
public void LogWarning(string data)
|
||||
{
|
||||
Log(LogLevel.Warning, data);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,122 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
import {Artifact, Cmd, Transformer, Tool} from "Sdk.Transformers";
|
||||
|
||||
export declare const qualifier: {};
|
||||
|
||||
const downloaderDefinition: Transformer.ToolDefinition = {
|
||||
// CODESYNC: keep in sync with deployment at Public\Src\FrontEnd\Nuget\BuildXL.FrontEnd.Nuget.dsc
|
||||
exe: Context.isWindowsOS()? f`${Context.getBuildEngineDirectory()}/NugetDownloader.exe` : f`${Context.getBuildEngineDirectory()}/NugetDownloader`,
|
||||
description: "BuildXL NuGet downloader",
|
||||
dependsOnWindowsDirectories: true,
|
||||
dependsOnAppDataDirectory: true,
|
||||
prepareTempDirectory: true,
|
||||
untrackedDirectoryScopes: [
|
||||
d`${Context.getMount("ProgramData").path}/Microsoft/NetFramework/BreadcrumbStore`,
|
||||
...addIfLazy(Context.isWindowsOS(), () => [d`${Context.getMount("LocalLow").path}/Microsoft/CryptnetUrlCache`]),
|
||||
...addIfLazy(Context.getCurrentHost().os === "macOS", () => [d`/dev/dtracehelper`, d`/private/var/run/mDNSResponder`, d`/private/var/folders`, d`/dev/tty`, d`/usr/share/zoneinfo`, d`/usr/share/icu`]),
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Arguments for downloading a NuGet package under a specified directory
|
||||
*/
|
||||
@@public
|
||||
export interface Arguments extends Transformer.RunnerArguments {
|
||||
id: string;
|
||||
version: string;
|
||||
downloadDirectory: Directory;
|
||||
extractedFiles: RelativePath[];
|
||||
repositories: [string, string][];
|
||||
credentialProviderPath?: File;
|
||||
timeoutInMinutes?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads a NuGet package as specified in the arguments and returns the outputs that result of the execution in a static directory
|
||||
*/
|
||||
@@public
|
||||
export function downloadPackage(args: Arguments) : PartialStaticContentDirectory {
|
||||
|
||||
const arguments: Argument[] = [
|
||||
Cmd.option("/id:", args.id),
|
||||
Cmd.option("/version:", args.version),
|
||||
Cmd.option("/downloadDirectory:", Artifact.none(args.downloadDirectory)),
|
||||
Cmd.options("/repositories:", args.repositories.map(kvp => kvp[0] + "=" + kvp[1])),
|
||||
];
|
||||
|
||||
// NuGet extraction always injects a parent folder with the package id + version as the name
|
||||
const directoryName = `${args.id}.${args.version}`;
|
||||
|
||||
// Build the list of expected outputs
|
||||
const outputs : File[] = args.extractedFiles.map(relativePath => f`${args.downloadDirectory}/${directoryName}/${relativePath}`);
|
||||
|
||||
// In some cases the package is expected to be empty. In that case, don't run anything and just return an empty seal dir
|
||||
if (outputs.length === 0)
|
||||
{
|
||||
return Transformer.sealPartialDirectory(d`${args.downloadDirectory.combine(directoryName)}`, []);
|
||||
}
|
||||
|
||||
let tool = downloaderDefinition;
|
||||
|
||||
// The timeout is not available per-pip but at the tool level
|
||||
if (args.timeoutInMinutes !== undefined) {
|
||||
tool = tool.merge<Transformer.ToolDefinition>({
|
||||
timeoutInMilliseconds: args.timeoutInMinutes * 60 * 1000,
|
||||
// We could have a separate argument for this, but for now let's keep it simple
|
||||
warningTimeoutInMilliseconds: args.timeoutInMinutes * 60 * 1000
|
||||
});
|
||||
}
|
||||
|
||||
// Nuget tries to read the user settings config file, and if it is not there, it creates it.
|
||||
// The creation does not behave well with concurrent executions and write locks issues arise.
|
||||
// Let's give each nuget pip a different app data directory
|
||||
const redirectedAppData = Context.getTempDirectory("redirectedAppData");
|
||||
|
||||
const transformerExecuteArgs : Transformer.ExecuteArguments = {
|
||||
description: `Downloading NuGet package '${args.id}.${args.version}'`,
|
||||
tool: tool,
|
||||
arguments: arguments,
|
||||
workingDirectory: args.downloadDirectory,
|
||||
tags: ["nuget", ...(args.tags || [])],
|
||||
environmentVariables: [
|
||||
...addIf(args.credentialProviderPath !== undefined, {name: "NUGET_CREDENTIALPROVIDERS_PATH", value: args.credentialProviderPath}),
|
||||
...addIf(Environment.hasVariable("VSS_NUGET_EXTERNAL_FEED_ENDPOINTS"), {name: "VSS_NUGET_EXTERNAL_FEED_ENDPOINTS", value: Environment.getStringValue("VSS_NUGET_EXTERNAL_FEED_ENDPOINTS")}),
|
||||
// On linux/mac the NuGet SDK needs this variable defined. It doesn't really matter where it is pointing to.
|
||||
...addIf(!Context.isWindowsOS(), {name: "DOTNET_CLI_HOME", value: redirectedAppData}),
|
||||
{name: "AppData", value: redirectedAppData}
|
||||
],
|
||||
outputs: outputs,
|
||||
tempDirectory: redirectedAppData,
|
||||
unsafe: <Transformer.UnsafeExecuteArguments>{
|
||||
passThroughEnvironmentVariables: [
|
||||
"LocalAppData",
|
||||
"QAUTHMATERIALROOT",
|
||||
"__CREDENTIAL_PROVIDER_LOG_DIR",
|
||||
"__CLOUDBUILD_AUTH_HELPER_ROOT__",
|
||||
"__CLOUDBUILD_AUTH_HELPER_CONFIG__",
|
||||
],
|
||||
requireGlobalDependencies: false,
|
||||
// The only child process this tool spawns is the credential provider process. This process may write log files that are hard to predict upfront.
|
||||
hasUntrackedChildProcesses: true,
|
||||
// This is to avoid a cache miss on a NuGet download due to changes on the nuget download tool (changes in downtream dependencies for the
|
||||
// most part, e.g. some framework dll that is changed because of some unrelated change in Bxl)
|
||||
// This is a confidence vote since we own the nuget downloader tool. If for any reason the nuget pips need invalidation, that can
|
||||
// be manually triggered by bumping the NuGet spec fingerprint version.
|
||||
untrackedScopes: [Context.getBuildEngineDirectory()],
|
||||
// Make NuGet pips so fingerprint salts (the general fingerprint V2 version and the debug salt passed with /p:BUILDXL_FINGERPRINT_SALT) are not
|
||||
// included in the weak fingerprint. Observe this option is not exposed in DScript, so we are forcing the hand of the type checker with the cast above
|
||||
// to not complain about this extra field (which will be picked up at runtime)
|
||||
bypassFingerprintSalt: true
|
||||
},
|
||||
// This is to avoid DFAs under the credential provider directory, which is an arbitrarily specified directory that would need to
|
||||
// be contained in a statically declared read mount otherwise
|
||||
allowUndeclaredSourceReads: true,
|
||||
};
|
||||
|
||||
const result = Transformer.execute(transformerExecuteArgs);
|
||||
|
||||
return Transformer.sealPartialDirectory(d`${args.downloadDirectory.combine(directoryName)}`, result.getOutputFiles());
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
module({
|
||||
name: "BuildXL.Tools.NugetDownloader"
|
||||
});
|
|
@ -1,149 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BuildXL.Native.IO;
|
||||
using BuildXL.ToolSupport;
|
||||
using BuildXL.Utilities;
|
||||
using BuildXL.Utilities.VstsAuthentication;
|
||||
using NuGet.Common;
|
||||
using NuGet.Configuration;
|
||||
using NuGet.Packaging;
|
||||
using NuGet.Packaging.Signing;
|
||||
using NuGet.Protocol.Core.Types;
|
||||
using NugetDownloader;
|
||||
|
||||
namespace Tool.Download
|
||||
{
|
||||
/// <summary>
|
||||
/// Downloads a Nuget into a given directory from a collection of source repositories
|
||||
/// </summary>
|
||||
internal sealed class NugetDownloader : ToolProgram<NugetDownloaderArgs>
|
||||
{
|
||||
private NugetDownloader() : base("NugetDownloader")
|
||||
{
|
||||
}
|
||||
|
||||
/// <nodoc />
|
||||
public static int Main(string[] arguments)
|
||||
{
|
||||
return new NugetDownloader().MainHandler(arguments);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool TryParse(string[] rawArgs, out NugetDownloaderArgs arguments)
|
||||
{
|
||||
try
|
||||
{
|
||||
arguments = new NugetDownloaderArgs(rawArgs);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(ex.GetLogEventMessage());
|
||||
arguments = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Run(NugetDownloaderArgs arguments)
|
||||
{
|
||||
return TryDownloadNugetToDiskAsync(arguments).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to download and extract a NuGet package to disk.
|
||||
/// </summary>
|
||||
private async Task<int> TryDownloadNugetToDiskAsync(NugetDownloaderArgs arguments)
|
||||
{
|
||||
Console.WriteLine($"Download started for package '{arguments.Id}' and version '{arguments.Version}' to '{arguments.DownloadDirectory}'.");
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
|
||||
SourceCacheContext cache = new SourceCacheContext();
|
||||
|
||||
var authLogger = new StringBuilder();
|
||||
var maybeRepositories = await VSTSAuthenticationHelper.TryCreateSourceRepositories(arguments.Repositories.Select(kvp => (kvp.Key, kvp.Value)), CancellationToken.None, authLogger);
|
||||
|
||||
// Informational data
|
||||
Console.WriteLine(authLogger.ToString());
|
||||
|
||||
if (!maybeRepositories.Succeeded)
|
||||
{
|
||||
Console.Error.WriteLine($"Failed to access the specified repositories: " + maybeRepositories.Failure.Describe());
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
ILogger logger = new ConsoleLogger();
|
||||
foreach (var sourceRepository in maybeRepositories.Result)
|
||||
{
|
||||
Console.Write($"Finding resource against source repository '{sourceRepository.PackageSource.Name}={sourceRepository.PackageSource.SourceUri}'. Authentication is on: {sourceRepository.PackageSource.Credentials != null}");
|
||||
|
||||
// TODO: maybe we need a retry here? or define a default retry in the DScript SDK?
|
||||
FindPackageByIdResource resource = await sourceRepository.GetResourceAsync<FindPackageByIdResource>();
|
||||
|
||||
if (resource == null)
|
||||
{
|
||||
Console.Write($"Resource under source repository '{sourceRepository.PackageSource.Name}={sourceRepository.PackageSource.SourceUri}' not found.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var destinationFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
|
||||
// We don't really need to materialize the nupkg file on disk, but a memory stream has a 2GB limitation, and NuGet packages
|
||||
// can be larger than that. Just place it on disk and remove it after we are done extracting.
|
||||
using (var packageStream = new FileStream(
|
||||
destinationFilePath,
|
||||
FileMode.Create,
|
||||
FileAccess.ReadWrite,
|
||||
FileShare.ReadWrite | FileShare.Delete,
|
||||
bufferSize: 4096))
|
||||
{
|
||||
if (await resource.CopyNupkgToStreamAsync(
|
||||
arguments.Id,
|
||||
arguments.Version,
|
||||
packageStream,
|
||||
cache,
|
||||
logger,
|
||||
CancellationToken.None))
|
||||
{
|
||||
found = true;
|
||||
|
||||
await PackageExtractor.ExtractPackageAsync(
|
||||
sourceRepository.PackageSource.Source,
|
||||
packageStream,
|
||||
new PackagePathResolver(arguments.DownloadDirectory),
|
||||
new PackageExtractionContext(
|
||||
PackageSaveMode.Files | PackageSaveMode.Nuspec,
|
||||
XmlDocFileSaveMode.None,
|
||||
ClientPolicyContext.GetClientPolicy(NullSettings.Instance, logger),
|
||||
logger),
|
||||
CancellationToken.None);
|
||||
|
||||
FileUtilities.DeleteFile(destinationFilePath);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
Console.Error.WriteLine($"Could not find NuGet package '{arguments.Id}' with version '{arguments.Version.ToNormalizedString()}' under any of the provided repositories.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Finished downloading package '{arguments.Id}' with version '{arguments.Version}' in {stopwatch.ElapsedMilliseconds}ms");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BuildXL.ToolSupport;
|
||||
using NuGet.Versioning;
|
||||
|
||||
namespace Tool.Download
|
||||
{
|
||||
/// <nodoc/>
|
||||
internal sealed partial class NugetDownloaderArgs : CommandLineUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// package id of the download
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// package version of the download
|
||||
/// </summary>
|
||||
public NuGetVersion Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The directory to place the downloaded file
|
||||
/// </summary>
|
||||
public string DownloadDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The Nuget feeds (name and Uri) where to download the package from
|
||||
/// </summary>
|
||||
public IReadOnlyDictionary<string, Uri> Repositories { get; }
|
||||
|
||||
/// <nodoc />
|
||||
public NugetDownloaderArgs(string[] args)
|
||||
: base(args)
|
||||
{
|
||||
var repositories = new Dictionary<string, Uri>();
|
||||
|
||||
foreach (Option opt in Options)
|
||||
{
|
||||
if (opt.Name.Equals("id", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Id = opt.Value;
|
||||
}
|
||||
else if (opt.Name.Equals("version", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!NuGetVersion.TryParse(opt.Value, out NuGetVersion version))
|
||||
{
|
||||
throw Error($"Malformed version: {opt.Value}.");
|
||||
}
|
||||
|
||||
Version = version;
|
||||
}
|
||||
else if (opt.Name.Equals("downloadDirectory", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
DownloadDirectory = opt.Value;
|
||||
}
|
||||
else if (opt.Name.Equals("repositories", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var kvp = ParseKeyValuePair(opt);
|
||||
repositories[kvp.Key] = new Uri(kvp.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Error($"Unsupported option: {opt.Name}.");
|
||||
}
|
||||
}
|
||||
|
||||
Repositories = repositories;
|
||||
|
||||
if (Id == null)
|
||||
{
|
||||
throw Error($"Missing mandatory argument 'id'");
|
||||
}
|
||||
|
||||
if (Version == null)
|
||||
{
|
||||
throw Error($"Missing mandatory argument 'version'");
|
||||
}
|
||||
|
||||
if (Repositories.Count == 0)
|
||||
{
|
||||
throw Error($"Missing mandatory argument 'repositories'");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(DownloadDirectory))
|
||||
{
|
||||
throw Error($"Missing mandatory argument 'DownloadDirectory'");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
import * as Managed from "Sdk.Managed";
|
||||
import * as BuildXLSdk from "Sdk.BuildXL";
|
||||
import {Transformer} from "Sdk.Transformers";
|
||||
import * as Deployment from "Sdk.Deployment";
|
||||
import * as MSBuild from "Sdk.Selfhost.MSBuild";
|
||||
import * as Frameworks from "Sdk.Managed.Frameworks";
|
||||
import * as Shared from "Sdk.Managed.Shared";
|
||||
|
||||
namespace NugetDownloader {
|
||||
|
||||
export declare const qualifier : BuildXLSdk.DefaultQualifierWithNet472;
|
||||
|
||||
// These are the specs that define the nuget downloader SDK. Since this is a bxl-provided tool
|
||||
// and customers don't have direct exposure to it, they are not places under the usual SDK folder, but
|
||||
// deployed as part of bxl binaries
|
||||
@@public
|
||||
export const specDeployment: Deployment.Definition = {
|
||||
contents: [
|
||||
{subfolder: r`Sdk/Sdk.NugetDownloader`, contents: [
|
||||
{file: f`LiteralFiles/Tool.NugetDownloader.dsc.literal`, targetFileName: a`Tool.NugetDownloader.dsc`},
|
||||
{file: f`LiteralFiles/module.config.dsc.literal`, targetFileName: a`module.config.dsc`},]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
@@public
|
||||
export const dll = BuildXLSdk.executable({
|
||||
assemblyName: "NugetDownloader",
|
||||
skipDocumentationGeneration: true,
|
||||
skipDefaultReferences: true,
|
||||
sources: globR(d`.`, "*.cs"),
|
||||
references:[
|
||||
...addIf(BuildXLSdk.isFullFramework,
|
||||
NetFx.Netstandard.dll
|
||||
),
|
||||
importFrom("BuildXL.Utilities").VstsAuthentication.dll,
|
||||
importFrom("BuildXL.Utilities").ToolSupport.dll,
|
||||
importFrom("BuildXL.Utilities").Native.dll,
|
||||
importFrom("BuildXL.Utilities").dll,
|
||||
|
||||
importFrom("NuGet.Versioning").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Protocol").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Configuration").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Common").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Frameworks").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Packaging").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
],
|
||||
runtimeContentToSkip:[
|
||||
importFrom("Newtonsoft.Json").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
],
|
||||
runtimeContent: [
|
||||
f`App.config`,
|
||||
specDeployment
|
||||
],
|
||||
deploymentOptions: { ignoredSelfContainedRuntimeFilenames: [a`System.Collections.Immutable.dll`] },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -11,8 +11,8 @@ namespace BuildXL.Utilities.Configuration
|
|||
public partial interface INugetConfiguration : IArtifactLocation
|
||||
{
|
||||
/// <summary>
|
||||
/// The download timeout, in minutes, for each NuGet download pip
|
||||
/// Optional credential helper to use for NuGet
|
||||
/// </summary>
|
||||
int? DownloadTimeoutMin { get; }
|
||||
IReadOnlyList<IArtifactLocation> CredentialProviders { get; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ namespace BuildXL.Utilities.Configuration.Mutable
|
|||
Repositories = new Dictionary<string, string>();
|
||||
Packages = new List<INugetPackage>();
|
||||
DoNotEnforceDependencyVersions = false;
|
||||
Configuration = new NugetConfiguration();
|
||||
}
|
||||
|
||||
/// <nodoc />
|
||||
|
@ -26,7 +25,7 @@ namespace BuildXL.Utilities.Configuration.Mutable
|
|||
Contract.Assume(template != null);
|
||||
Contract.Assume(pathRemapper != null);
|
||||
|
||||
Configuration = new NugetConfiguration(template.Configuration);
|
||||
Configuration = template.Configuration == null ? null : new NugetConfiguration(template.Configuration);
|
||||
Repositories = new Dictionary<string, string>(template.Repositories.Count);
|
||||
foreach (var kv in template.Repositories)
|
||||
{
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace BuildXL.Utilities.Configuration.Mutable
|
||||
{
|
||||
/// <nodoc />
|
||||
|
@ -9,17 +12,21 @@ namespace BuildXL.Utilities.Configuration.Mutable
|
|||
/// <nodoc />
|
||||
public NugetConfiguration()
|
||||
{
|
||||
DownloadTimeoutMin = 20;
|
||||
CredentialProviders = new List<IArtifactLocation>();
|
||||
}
|
||||
|
||||
/// <nodoc />
|
||||
public NugetConfiguration(INugetConfiguration template)
|
||||
: base(template)
|
||||
{
|
||||
DownloadTimeoutMin = template.DownloadTimeoutMin;
|
||||
CredentialProviders = new List<IArtifactLocation>(template.CredentialProviders);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int? DownloadTimeoutMin { get; set; }
|
||||
/// <nodoc />
|
||||
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
||||
public List<IArtifactLocation> CredentialProviders { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
IReadOnlyList<IArtifactLocation> INugetConfiguration.CredentialProviders => CredentialProviders;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace Native {
|
|||
export const nativeLinux = [
|
||||
...addIfLazy(qualifier.targetRuntime === "linux-x64", () =>
|
||||
[
|
||||
...importFrom("runtime.linux-x64.BuildXL").Contents.all.ensureContents({subFolder: r`runtimes/linux-x64/native/${qualifier.configuration}`}).getContent()
|
||||
...globR(d`${importFrom("runtime.linux-x64.BuildXL").Contents.all.root}/runtimes/linux-x64/native/${qualifier.configuration}`, "*.so")
|
||||
]),
|
||||
];
|
||||
|
||||
|
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System.IO;
|
||||
using BuildXL.Utilities;
|
||||
using Test.BuildXL.TestUtilities.Xunit;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace Test.BuildXL.Utilities
|
||||
{
|
||||
public class ZeroLeftPaddedStreamTests : XunitBuildXLTest
|
||||
{
|
||||
public ZeroLeftPaddedStreamTests(ITestOutputHelper output)
|
||||
: base(output) { }
|
||||
|
||||
[Fact]
|
||||
public void TestBasicFunctionality()
|
||||
{
|
||||
var underlyingStream = new MemoryStream(new byte[5] { 1, 2, 3, 4, 5 });
|
||||
var paddedStream = new ZeroLeftPaddedStream(underlyingStream, 10);
|
||||
|
||||
// Total length should be 10, even if the underlying stream is smaller
|
||||
XAssert.AreEqual(10, paddedStream.Length);
|
||||
|
||||
// Check Position works
|
||||
paddedStream.Position = 9;
|
||||
XAssert.AreEqual(9, paddedStream.Position);
|
||||
|
||||
// Read the whole stream. We should get zeros in the first 5 positions
|
||||
paddedStream.Position = 0;
|
||||
XAssert.AreEqual(0, paddedStream.Position);
|
||||
var result = new byte[10];
|
||||
var bytesRead = paddedStream.Read(result, 0, 10);
|
||||
XAssert.AreArraysEqual(new byte[] { 0, 0, 0, 0, 0, 1, 2, 3, 4, 5 }, result, true);
|
||||
XAssert.AreEqual(10, bytesRead);
|
||||
|
||||
// Read a part of the stream offsetting the destination
|
||||
System.Array.Clear(result, 0, 10);
|
||||
paddedStream.Position = 4;
|
||||
bytesRead = paddedStream.Read(result, 1, 3);
|
||||
XAssert.AreArraysEqual(new byte[] { 0, 0, 1, 2, 0, 0, 0, 0, 0, 0 }, result, true);
|
||||
XAssert.AreEqual(3, bytesRead);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestSeek()
|
||||
{
|
||||
var underlyingStream = new MemoryStream(new byte[5] { 1, 2, 3, 4, 5 });
|
||||
var paddedStream = new ZeroLeftPaddedStream(underlyingStream, 10);
|
||||
|
||||
var result = new byte[2];
|
||||
|
||||
var newPos = paddedStream.Seek(4, SeekOrigin.Begin);
|
||||
XAssert.AreEqual(4, newPos);
|
||||
paddedStream.Read(result, 0, 2);
|
||||
XAssert.AreEqual(0, result[0]);
|
||||
XAssert.AreEqual(1, result[1]);
|
||||
|
||||
newPos = paddedStream.Seek(7, SeekOrigin.Begin);
|
||||
XAssert.AreEqual(7, newPos);
|
||||
paddedStream.Read(result, 0, 2);
|
||||
XAssert.AreEqual(3, result[0]);
|
||||
XAssert.AreEqual(4, result[1]);
|
||||
|
||||
newPos = paddedStream.Seek(-1, SeekOrigin.End);
|
||||
XAssert.AreEqual(9, newPos);
|
||||
paddedStream.Read(result, 0, 1);
|
||||
XAssert.AreEqual(5, result[0]);
|
||||
|
||||
newPos = paddedStream.Seek(-6, SeekOrigin.End);
|
||||
XAssert.AreEqual(4, newPos);
|
||||
paddedStream.Read(result, 0, 2);
|
||||
XAssert.AreEqual(0, result[0]);
|
||||
XAssert.AreEqual(1, result[1]);
|
||||
|
||||
paddedStream.Position = 6;
|
||||
newPos = paddedStream.Seek(-2, SeekOrigin.Current);
|
||||
XAssert.AreEqual(4, newPos);
|
||||
paddedStream.Read(result, 0, 2);
|
||||
XAssert.AreEqual(0, result[0]);
|
||||
XAssert.AreEqual(1, result[1]);
|
||||
|
||||
paddedStream.Position = 4;
|
||||
newPos = paddedStream.Seek(2, SeekOrigin.Current);
|
||||
XAssert.AreEqual(6, newPos);
|
||||
paddedStream.Read(result, 0, 2);
|
||||
XAssert.AreEqual(2, result[0]);
|
||||
XAssert.AreEqual(3, result[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.ContractsLight;
|
||||
using System.IO;
|
||||
|
||||
namespace BuildXL.Utilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a seekable stream to expose it with a configurable minimum size, left padding with zeros if needed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// No extra memory is taken by the padded portion of the stream
|
||||
/// If the underlying stream is modified, then the behavior of this class is non-deterministic
|
||||
/// </remarks>
|
||||
public class ZeroLeftPaddedStream : Stream
|
||||
{
|
||||
private readonly Stream m_stream;
|
||||
private long m_currentPos = 0;
|
||||
private readonly long m_gap;
|
||||
|
||||
/// <nodoc/>
|
||||
public ZeroLeftPaddedStream(Stream stream, long minimumLength = 0)
|
||||
{
|
||||
Contract.RequiresNotNull(stream);
|
||||
Contract.Requires(stream.CanSeek);
|
||||
|
||||
m_stream = stream;
|
||||
MinimumLength = minimumLength;
|
||||
m_gap = Math.Max(0, MinimumLength - m_stream.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The miminum legth the stream has
|
||||
/// </summary>
|
||||
public long MinimumLength { set; get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanRead => m_stream.CanRead;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool CanSeek => m_stream.CanSeek;
|
||||
|
||||
/// <summary>
|
||||
/// Always false
|
||||
/// </summary>
|
||||
public override bool CanWrite => false;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Length => m_stream.Length + m_gap;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Position {
|
||||
get => m_currentPos;
|
||||
set
|
||||
{
|
||||
m_stream.Position = Math.Max(0, value - m_gap);
|
||||
m_currentPos = Math.Min(value, Length);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override void Flush()
|
||||
{
|
||||
m_stream.Flush();
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
int bytesRead = 0;
|
||||
|
||||
if (Position < m_gap)
|
||||
{
|
||||
// Produce the padded area of the stream with 0-bytes
|
||||
long maxPaddedCount = m_gap - Position;
|
||||
int paddedCount = Math.Min(count, maxPaddedCount > int.MaxValue ? int.MaxValue : (int) maxPaddedCount);
|
||||
if (paddedCount > 0)
|
||||
{
|
||||
Array.Clear(buffer, offset, paddedCount);
|
||||
bytesRead = paddedCount;
|
||||
}
|
||||
|
||||
// Read the reminder, if any, from the underlying stream
|
||||
if (paddedCount < count)
|
||||
{
|
||||
bytesRead += m_stream.Read(buffer, offset + paddedCount, count - paddedCount);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// The position is beyond the padded area, just do a regular read on the underlying stream
|
||||
bytesRead = m_stream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
Position += count;
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
// Compute the origin of the seek in terms of the starting index on the stream
|
||||
long originIndex = origin switch
|
||||
{
|
||||
SeekOrigin.Begin => 0,
|
||||
SeekOrigin.Current => Position,
|
||||
SeekOrigin.End => Length,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(origin), $"Not expected value {origin}")
|
||||
};
|
||||
|
||||
// This is the final index of the stream
|
||||
long finalIndex = Math.Min(Length, originIndex + offset);
|
||||
|
||||
if (finalIndex < 0)
|
||||
{
|
||||
throw new ArgumentException("Attempt to seek before the beginning of the stream");
|
||||
}
|
||||
|
||||
if (finalIndex >= m_gap)
|
||||
{
|
||||
m_stream.Seek(finalIndex - m_gap, SeekOrigin.Begin);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stream.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
Position = finalIndex;
|
||||
|
||||
return Position;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always throws
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shouldn't be called since this stream is non-writable
|
||||
/// </remarks>
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Always throws
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Shouldn't be called since this stream is non-writable
|
||||
/// </remarks>
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
namespace BuildXL.Utilities.VstsAuthentication
|
||||
{
|
||||
/// <summary>
|
||||
/// An authentication related failure when dealing with authenticated NuGet feeds
|
||||
/// </summary>
|
||||
public class AuthenticationFailure : Failure
|
||||
{
|
||||
private readonly string m_failure;
|
||||
|
||||
/// <nodoc/>
|
||||
public AuthenticationFailure(string failure)
|
||||
{
|
||||
m_failure = failure;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override BuildXLException CreateException()
|
||||
{
|
||||
return new BuildXLException(m_failure);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string Describe()
|
||||
{
|
||||
return m_failure;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override BuildXLException Throw()
|
||||
{
|
||||
throw CreateException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
|
||||
namespace VstsAuthentication {
|
||||
@@public
|
||||
export const dll = BuildXLSdk.library({
|
||||
assemblyName: "BuildXL.Utilities.VstsAuthentication",
|
||||
sources: globR(d`.`, "*.cs"),
|
||||
references: [
|
||||
$.dll,
|
||||
Native.dll,
|
||||
importFrom("Newtonsoft.Json").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Versioning").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Protocol").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Configuration").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Common").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Frameworks").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
importFrom("NuGet.Packaging").withQualifier({targetFramework: "netstandard2.0"}).pkg,
|
||||
...addIfLazy(BuildXLSdk.isFullFramework, () => [
|
||||
NetFx.System.Net.Http.dll,
|
||||
NetFx.Netstandard.dll,
|
||||
]),
|
||||
],
|
||||
});
|
||||
}
|
|
@ -1,334 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Diagnostics.ContractsLight;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NuGet.Configuration;
|
||||
using NuGet.Protocol;
|
||||
using NuGet.Protocol.Core.Types;
|
||||
using System.Net.Http;
|
||||
using BuildXL.Native.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace BuildXL.Utilities.VstsAuthentication
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper to get authentication credentials against VSTS connections
|
||||
/// </summary>
|
||||
public static class VSTSAuthenticationHelper
|
||||
{
|
||||
// Constant value to target Azure DevOps.
|
||||
private const string Resource = "499b84ac-1321-427f-aa17-267ca6975798";
|
||||
// Visual Studio IDE client ID originally provisioned by Azure Tools.
|
||||
private const string Client = "872cd9fa-d31f-45e0-9eab-6e460a02d1f1";
|
||||
// Microsoft tenant
|
||||
private const string Tenant = "microsoft.com";
|
||||
// Microsoft authority
|
||||
private const string Authority = $"https://login.windows.net/microsoft.com";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a collection of <see cref="SourceRepository"/> given a collection of URIs
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The credential provider is discovered by searching under the paths specified in NUGET_CREDENTIALPROVIDERS_PATH environment variable
|
||||
/// </remarks>
|
||||
public static Task<Possible<IReadOnlyCollection<SourceRepository>>> TryCreateSourceRepositories(
|
||||
IEnumerable<(string repositoryName, Uri repositoryUri)> repositories,
|
||||
CancellationToken cancellationToken,
|
||||
StringBuilder logger)
|
||||
{
|
||||
return TryCreateSourceRepositories(repositories, TryDiscoverCredentialProvider, cancellationToken, logger);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a collection of <see cref="SourceRepository"/> given a collection of URIs
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// For the repositories identified as VSTS ones, credential provider authentication is performed
|
||||
/// </remarks>
|
||||
public static async Task<Possible<IReadOnlyCollection<SourceRepository>>> TryCreateSourceRepositories(
|
||||
IEnumerable<(string repositoryName, Uri repositoryUri)> repositories,
|
||||
Func<Possible<string>> discoverCredentialProvider,
|
||||
CancellationToken cancellationToken,
|
||||
StringBuilder logger)
|
||||
{
|
||||
Contract.RequiresNotNull(repositories);
|
||||
|
||||
var sourceRepositories = new List<SourceRepository>();
|
||||
|
||||
Lazy<Possible<string>> credentialProviderPath = new Lazy<Possible<string>>(discoverCredentialProvider);
|
||||
|
||||
// For each repository, authenticate if we are dealing with a VSTS feed and retrieve the base addresses
|
||||
foreach (var repository in repositories)
|
||||
{
|
||||
Uri uri = repository.repositoryUri;
|
||||
string uriAsString = uri.AbsoluteUri;
|
||||
var packageSource = new PackageSource(uriAsString, repository.repositoryName);
|
||||
|
||||
logger.AppendLine($"Analyzing {uri.AbsoluteUri}");
|
||||
// For now we only support authentication against VSTS feeds. Feeds outside of that will attempt to connect unauthenticated
|
||||
if (IsVSTSPackageSecureURI(uri) && await IsAuthenticationRequiredAsync(uri, cancellationToken, logger))
|
||||
{
|
||||
logger.AppendLine($"Authentication required for {uri.AbsoluteUri}. Trying to acquire credentials.");
|
||||
|
||||
if (!credentialProviderPath.Value.Succeeded)
|
||||
{
|
||||
return credentialProviderPath.Value.Failure;
|
||||
}
|
||||
|
||||
if (await TryGetAuthenticationCredentialsAsync(uri, credentialProviderPath.Value.Result, cancellationToken) is var maybeCredentials)
|
||||
{
|
||||
var maybePackageSourceCredential = maybeCredentials
|
||||
.Then(credentials => new PackageSourceCredential(uriAsString, credentials.username, credentials.pat, true, string.Empty));
|
||||
|
||||
if (maybePackageSourceCredential.Succeeded)
|
||||
{
|
||||
logger.AppendLine($"Authentication successfully acquired for {uri.AbsoluteUri}");
|
||||
|
||||
packageSource.Credentials = maybePackageSourceCredential.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.AppendLine($"Failed to authenticate {uri.AbsoluteUri}: {maybePackageSourceCredential.Failure.DescribeIncludingInnerFailures()}");
|
||||
return maybePackageSourceCredential.Failure;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.AppendLine($"Authentication not required for {uri.AbsoluteUri}");
|
||||
|
||||
var sourceRepository = Repository.Factory.GetCoreV3(packageSource);
|
||||
sourceRepositories.Add(sourceRepository);
|
||||
}
|
||||
|
||||
return sourceRepositories;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the provided Uri requires authentication
|
||||
/// </summary>
|
||||
public static async Task<bool> IsAuthenticationRequiredAsync(Uri uri, CancellationToken cancellationToken, StringBuilder logger)
|
||||
{
|
||||
logger.AppendLine($"Checking whether authentication is required for {uri.AbsoluteUri}");
|
||||
try
|
||||
{
|
||||
var handler = new HttpClientHandler()
|
||||
{
|
||||
AllowAutoRedirect = false
|
||||
};
|
||||
|
||||
using (var httpClient = new HttpClient(handler))
|
||||
{
|
||||
httpClient.Timeout = TimeSpan.FromSeconds(5);
|
||||
|
||||
int retries = 3;
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpResponse = await httpClient.GetAsync(uri, cancellationToken);
|
||||
|
||||
|
||||
logger.AppendLine($"Response for {uri.AbsoluteUri} is: {httpResponse.StatusCode}");
|
||||
|
||||
return (httpResponse.StatusCode == System.Net.HttpStatusCode.Unauthorized || httpResponse.StatusCode == System.Net.HttpStatusCode.Redirect) && httpResponse.Headers.WwwAuthenticate.Any();
|
||||
}
|
||||
catch (TaskCanceledException)
|
||||
{
|
||||
logger.AppendLine($"Request for {uri.AbsoluteUri} timed out. Retries left: {retries}");
|
||||
|
||||
retries--;
|
||||
|
||||
// The service seems to be unavailable. Let the pipeline deal with the unavailability, since it will likely result in a better error message
|
||||
if (retries == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException || ex is AggregateException)
|
||||
{
|
||||
logger.AppendLine($"Exception for {uri.AbsoluteUri}: {ex.Message}");
|
||||
|
||||
// The service seems to be have a problem. Let's pretend it does need auth and let the rest of the pipeline deal with the unavailability, since it will likely result in a better error message
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the URI is pointing to a VSTS URI
|
||||
/// </summary>
|
||||
public static bool IsVSTSPackageSecureURI(Uri downloadURI)
|
||||
{
|
||||
return downloadURI.Scheme == "https" &&
|
||||
(downloadURI.Host.EndsWith("pkgs.visualstudio.com", StringComparison.OrdinalIgnoreCase) || downloadURI.Host.EndsWith("pkgs.dev.azure.com", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns an HTTP-based header from a PAT, which can be injected in an HTTP header request message
|
||||
/// </summary>
|
||||
public static AuthenticationHeaderValue GetAuthenticationHeaderFromPAT(string pat)
|
||||
{
|
||||
return new AuthenticationHeaderValue("Basic",
|
||||
Convert.ToBase64String(
|
||||
System.Text.ASCIIEncoding.ASCII.GetBytes(
|
||||
string.Format("{0}:{1}", "", pat))));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="TryGetAuthenticationCredentialsAsync(Uri, string, CancellationToken, bool)"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The credential provider is discovered by searching under the paths specified in NUGET_CREDENTIALPROVIDERS_PATH environment variable
|
||||
/// </remarks>
|
||||
public static Task<Possible<(string username, string pat)>> TryGetAuthenticationCredentialsAsync(
|
||||
Uri uri,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
return TryDiscoverCredentialProvider()
|
||||
.ThenAsync(async credentialProviderPath => await TryGetAuthenticationCredentialsAsync(uri, credentialProviderPath, cancellationToken));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get an authentication token using a credential provider
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The credential provider is discovered following the definition here: https://docs.microsoft.com/en-us/nuget/reference/extensibility/nuget-exe-credential-providers
|
||||
/// using an environment variable.
|
||||
/// Observe the link above is for nuget specifically, but the authentication here is used for VSTS feeds in general
|
||||
/// </remarks>
|
||||
public static Task<Possible<(string username, string pat)>> TryGetAuthenticationCredentialsAsync(
|
||||
Uri uri,
|
||||
string credentialProviderPath,
|
||||
CancellationToken cancellationToken,
|
||||
bool forceJson = false)
|
||||
{
|
||||
// Call the provider with the requested URI in non-interactive mode.
|
||||
// Some credential providers support specifying JSON as the output format. We first try without this option (some other providers will fail if specified)
|
||||
// but if the provider succeeds but we can't recognize the output as a JSON one, we try again with this option set
|
||||
var processInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = credentialProviderPath,
|
||||
Arguments = $"-Uri {uri.AbsoluteUri} -NonInteractive{(forceJson? " -F JSON" : string.Empty)}",
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
};
|
||||
|
||||
return RunCredentialProvider(uri, credentialProviderPath, processInfo, forceJson, cancellationToken);
|
||||
}
|
||||
|
||||
private static async Task<Possible<(string username, string pat)>> RunCredentialProvider(
|
||||
Uri uri,
|
||||
string credentialProviderPath,
|
||||
ProcessStartInfo processInfo,
|
||||
bool jsonWasForced,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var failureCommon = $"Unable to authenticate using a credential provider for '{uri}':";
|
||||
|
||||
using (var process = Process.Start(processInfo))
|
||||
{
|
||||
try
|
||||
{
|
||||
#pragma warning disable AsyncFixer02 // WaitForExitAsync should be used instead
|
||||
process?.WaitForExit();
|
||||
#pragma warning restore AsyncFixer02
|
||||
}
|
||||
catch (Exception e) when (e is SystemException || e is Win32Exception)
|
||||
{
|
||||
return new AuthenticationFailure($"{failureCommon} Credential provider execution '{credentialProviderPath}' failed. Error: {e.Message}");
|
||||
}
|
||||
|
||||
if (process == null)
|
||||
{
|
||||
// The process was not started
|
||||
return new AuthenticationFailure($"{failureCommon} Could not start credential provider process '{credentialProviderPath}'.");
|
||||
}
|
||||
|
||||
// Check whether the authorization succeeded
|
||||
if (process.ExitCode == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
// The response should be a well-formed JSON with a 'password' entry representing the PAT
|
||||
var response = (JObject)await JToken.ReadFromAsync(new JsonTextReader(process.StandardOutput), cancellationToken);
|
||||
var pat = response.GetValue("Password");
|
||||
|
||||
if (pat == null)
|
||||
{
|
||||
return new AuthenticationFailure($"{failureCommon} Could not find a 'password' entry in the JSON response of '{credentialProviderPath}'.");
|
||||
}
|
||||
|
||||
// We got a PAT back. Create an authentication header with a base64 encoding of the retrieved PAT
|
||||
return (response.GetValue("Username")?.ToString(), pat.ToString());
|
||||
|
||||
}
|
||||
catch (JsonReaderException)
|
||||
{
|
||||
// The JSON is malformed. If we were already forcing JSON, then that's an error. Otherwise, we try again but now forcing JSON
|
||||
if (jsonWasForced)
|
||||
{
|
||||
return new AuthenticationFailure($"{failureCommon} The credential provider '{credentialProviderPath}' response is not a well-formed JSON.");
|
||||
}
|
||||
|
||||
return await TryGetAuthenticationCredentialsAsync(uri, credentialProviderPath, cancellationToken, forceJson: true);
|
||||
}
|
||||
}
|
||||
|
||||
return new AuthenticationFailure($"{failureCommon} The credential provider '{credentialProviderPath}' returned a non-succesful exit code: {process.ExitCode}.");
|
||||
}
|
||||
}
|
||||
|
||||
private static Possible<string> TryDiscoverCredentialProvider()
|
||||
{
|
||||
var credentialProviderPaths = Environment.GetEnvironmentVariable("NUGET_CREDENTIALPROVIDERS_PATH");
|
||||
|
||||
if (credentialProviderPaths == null)
|
||||
{
|
||||
return new AuthenticationFailure("Unable to authenticate using a credential provider: NUGET_CREDENTIALPROVIDERS_PATH is not set.");
|
||||
}
|
||||
|
||||
// Here we do something slightly simpler than what NuGet does and just look for the first credential
|
||||
// provider we can find
|
||||
string credentialProviderPath = null;
|
||||
foreach (string path in credentialProviderPaths.Split(new[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
string directory = path;
|
||||
|
||||
// In some cases the entry can point directly to the credential .exe. Let's treat that uniformly and enumerate its parent directory
|
||||
if (FileUtilities.TryProbePathExistence(path, followSymlink: false) is var maybeExistence &&
|
||||
maybeExistence.Succeeded &&
|
||||
maybeExistence.Result == PathExistence.ExistsAsFile)
|
||||
{
|
||||
directory = Directory.GetParent(path).FullName;
|
||||
}
|
||||
|
||||
credentialProviderPath = Directory.EnumerateFiles(directory, "CredentialProvider*.exe", SearchOption.TopDirectoryOnly).FirstOrDefault();
|
||||
if (credentialProviderPath != null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (credentialProviderPath == null)
|
||||
{
|
||||
return new AuthenticationFailure($"Unable to authenticate using a credential provider: Credential provider was not found under '{credentialProviderPaths}'.");
|
||||
}
|
||||
|
||||
return credentialProviderPath;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,3 @@
|
|||
set BUILDXL_LKG_NAME=BuildXL.win-x64
|
||||
set BUILDXL_LKG_VERSION=0.1.0-20220512.5
|
||||
set BUILDXL_LKG_VERSION=0.1.0-20220511.3
|
||||
set BUILDXL_LKG_FEED_1=https://pkgs.dev.azure.com/1essharedassets/_packaging/BuildXL/nuget/v3/index.json
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
set BUILDXL_LKG_NAME=Microsoft.BuildXL.win-x64
|
||||
set BUILDXL_LKG_VERSION=0.1.0-20220512.5
|
||||
set BUILDXL_LKG_VERSION=0.1.0-20220511.3
|
||||
set BUILDXL_LKG_FEED_1=https://pkgs.dev.azure.com/ms/BuildXL/_packaging/BuildXL/nuget/v3/index.json
|
||||
|
|
|
@ -114,7 +114,6 @@ REM *********************************
|
|||
REM We'll conditionally set the credential provider if not set on the machine.
|
||||
REM If not set we will set it to the local one in the enlistment but iwth the b-drive substitution
|
||||
if NOT DEFINED NUGET_CREDENTIALPROVIDERS_PATH (
|
||||
ECHO NUGET_CREDENTIALPROVIDERS_PATH not set. Setting it to %TOOLROOT%
|
||||
set NUGET_CREDENTIALPROVIDERS_PATH=%TOOLROOT%
|
||||
)
|
||||
goto :EOF
|
||||
|
|
Двоичные данные
Shared/Tools/Microsoft.IdentityModel.Clients.ActiveDirectory.dll
Двоичные данные
Shared/Tools/Microsoft.IdentityModel.Clients.ActiveDirectory.dll
Двоичный файл не отображается.
Двоичные данные
Shared/Tools/Newtonsoft.Json.dll
Двоичные данные
Shared/Tools/Newtonsoft.Json.dll
Двоичный файл не отображается.
Двоичные данные
Shared/Tools/NuGet.Common.dll
Двоичные данные
Shared/Tools/NuGet.Common.dll
Двоичный файл не отображается.
Двоичные данные
Shared/Tools/NuGet.Protocol.dll
Двоичные данные
Shared/Tools/NuGet.Protocol.dll
Двоичный файл не отображается.
Двоичные данные
Shared/Tools/PowerArgs.dll
Двоичные данные
Shared/Tools/PowerArgs.dll
Двоичный файл не отображается.
|
@ -1,3 +0,0 @@
|
|||
These files are used by:
|
||||
- bxl.ps1 in order to retrieve the latest bxl.exe from the corresponding feed and bootstrap the build with the latest LKG. Nuget.exe and the checked-in credential provider are used to authenticate against the VSTS feed.
|
||||
- bxl.ps1 script also sets NUGET_CREDENTIALPROVIDERS_PATH environment variable to point to this folder (if not previously set). That implies that the Nuget resolver, if configured, will also use this credential provider to authenticate.
|
|
@ -2769,25 +2769,7 @@
|
|||
"Type": "NuGet",
|
||||
"NuGet": {
|
||||
"Name": "NuGet.CommandLine",
|
||||
"Version": "5.11.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Component": {
|
||||
"Type": "NuGet",
|
||||
"NuGet": {
|
||||
"Name": "NuGet.Common",
|
||||
"Version": "5.11.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Component": {
|
||||
"Type": "NuGet",
|
||||
"NuGet": {
|
||||
"Name": "NuGet.Configuration",
|
||||
"Version": "5.11.0"
|
||||
"Version": "4.7.1"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2796,25 +2778,7 @@
|
|||
"Type": "NuGet",
|
||||
"NuGet": {
|
||||
"Name": "NuGet.Frameworks",
|
||||
"Version": "5.11.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Component": {
|
||||
"Type": "NuGet",
|
||||
"NuGet": {
|
||||
"Name": "NuGet.Packaging",
|
||||
"Version": "5.11.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"Component": {
|
||||
"Type": "NuGet",
|
||||
"NuGet": {
|
||||
"Name": "NuGet.Protocol",
|
||||
"Version": "5.11.0"
|
||||
"Version": "5.0.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2823,7 +2787,7 @@
|
|||
"Type": "NuGet",
|
||||
"NuGet": {
|
||||
"Name": "NuGet.Versioning",
|
||||
"Version": "5.11.0"
|
||||
"Version": "4.6.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
20
config.dsc
20
config.dsc
|
@ -38,10 +38,11 @@ config({
|
|||
]
|
||||
},
|
||||
{
|
||||
// The credential provider should be set by defining the env variable NUGET_CREDENTIALPROVIDERS_PATH.
|
||||
kind: "Nuget",
|
||||
|
||||
// TODO: remove once the new LKG gets deployed, nuget.exe is not used anymore
|
||||
// This configuration pins people to a specific version of nuget
|
||||
// The credential provider should be set by defining the env variable NUGET_CREDENTIALPROVIDERS_PATH. TODO: It can be alternatively pinned here,
|
||||
// but when it fails to download (e.g. from a share) the build is aborted. Consider making the failure non-blocking.
|
||||
configuration: {
|
||||
toolUrl: "https://dist.nuget.org/win-x86-commandline/v4.9.4/NuGet.exe",
|
||||
hash: "VSO0:17E8C8C0CDCCA3A6D1EE49836847148C4623ACEA5E6E36E10B691DA7FDC4C39200"
|
||||
|
@ -59,7 +60,10 @@ config({
|
|||
}
|
||||
: {
|
||||
"buildxl-selfhost" : "https://pkgs.dev.azure.com/ms/BuildXL/_packaging/BuildXL.Selfhost/nuget/v3/index.json",
|
||||
"nuget.org" : "https://api.nuget.org/v3/index.json",
|
||||
"nuget.org" : "http://api.nuget.org/v3/index.json",
|
||||
"roslyn-tools" : "https://dotnet.myget.org/F/roslyn-tools/api/v3/index.json",
|
||||
"msbuild" : "https://dotnet.myget.org/F/msbuild/api/v3/index.json",
|
||||
"dotnet-core" : "https://dotnet.myget.org/F/dotnet-core/api/v3/index.json",
|
||||
"dotnet-arcade" : "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json",
|
||||
},
|
||||
|
||||
|
@ -160,13 +164,9 @@ config({
|
|||
{ id: "System.Threading.Tasks.Dataflow", version: "4.9.0" },
|
||||
|
||||
// Nuget
|
||||
{ id: "NuGet.Packaging", version: "5.11.0", dependentPackageIdsToSkip: ["System.Security.Cryptography.ProtectedData", "System.Security.Cryptography.Pkcs"] },
|
||||
{ id: "NuGet.Configuration", version: "5.11.0", dependentPackageIdsToSkip: ["System.Security.Cryptography.ProtectedData"] },
|
||||
{ id: "NuGet.Common", version: "5.11.0" },
|
||||
{ id: "NuGet.Protocol", version: "5.11.0" },
|
||||
{ id: "NuGet.Versioning", version: "5.11.0" },
|
||||
{ id: "NuGet.CommandLine", version: "5.11.0" },
|
||||
{ id: "NuGet.Frameworks", version: "5.11.0"}, // needed for qtest on .net core
|
||||
{ id: "NuGet.CommandLine", version: "4.7.1" },
|
||||
{ id: "NuGet.Versioning", version: "4.6.0" }, // Can't use the latest becuase nuget extracts to folder with metadata which we don't support yet.
|
||||
{ id: "NuGet.Frameworks", version: "5.0.0"}, // needed for qtest on .net core
|
||||
|
||||
// ProjFS (virtual file system)
|
||||
{ id: "Microsoft.Windows.ProjFS", version: "1.2.19351.1" },
|
||||
|
|
Загрузка…
Ссылка в новой задаче