|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// THE ASSEMBLY BUILT FROM THIS SOURCE FILE HAS BEEN DEPRECATED FOR YEARS. IT IS BUILT ONLY TO PROVIDE
// BACKWARD COMPATIBILITY FOR API USERS WHO HAVE NOT YET MOVED TO UPDATED APIS. PLEASE DO NOT SEND PULL
// REQUESTS THAT CHANGE THIS FILE WITHOUT FIRST CHECKING WITH THE MAINTAINERS THAT THE FIX IS REQUIRED.
using System;
using System.Collections;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Build.Construction;
namespace Microsoft.Build.Conversion
{
/// <summary>
/// Only these switches will be migrated.
/// This enum doesnt have any use, except to explicitly list the compiler
/// options in AdditionalOptions project property that will be migrated
/// </summary>
internal enum SwitchesToMigrate
{
STM_CodePage,
STM_DisableLangExtensions,
STM_Jcpa,
STM_LinkResource,
STM_SecureScoping,
STM_Win32Resource,
};
/// <summary>
/// These are types of values associated with the switches
/// </summary>
internal enum SwitchValueType
{
/// <summary>
/// Boolean value
/// </summary>
SVT_Boolean,
/// <summary>
/// String value
/// </summary>
SVT_String,
/// <summary>
/// This switch can occur multiple times and the
/// final value is the ';' delimeted concat of all the
/// individual occurrences
/// </summary>
SVT_MultiString,
}
/// <summary>
/// This class contains the migration info for a switch
/// that we want to migrate
/// </summary>
internal sealed class CompSwitchInfo
{
/// <summary>
/// This is the internal switch identifier
/// Examples:
/// 1. STM_SecureScoping
/// </summary>
internal SwitchesToMigrate Switch;
/// <summary>
/// This is the string passed to the compiler
/// Examples:
/// 1. /ss, /securescoping
/// 2. @
/// </summary>
internal string[] SwitchIDs;
/// <summary>
/// This is the type of the value associated with the switch
/// Examples:
/// 1. SVT_Boolean
/// 2. SVT_MultiString
/// </summary>
internal SwitchValueType SwitchValueType;
/// <summary>
/// This is the final value of the switch
/// 1. true
/// 2. "path-a;path-b\\file-b"
/// </summary>
internal object SwitchValue;
/// <summary>
/// This is the name of property in the project file in which the
/// value of this switch is stored
/// </summary>
internal string SwitchProjectPropertyName;
/// <summary>
/// The constructor
/// </summary>
internal CompSwitchInfo(
SwitchesToMigrate switchStr,
string[] switchIDs,
SwitchValueType switchValueType,
object switchValue,
string switchProjectPropertyName
)
{
this.Switch = switchStr;
this.SwitchIDs = switchIDs;
this.SwitchValueType = switchValueType;
this.SwitchValue = switchValue;
this.SwitchProjectPropertyName = switchProjectPropertyName;
}
}
/// <summary>
///
/// Class: AdditionalOptionsParser
/// Owner: ParthaD
///
/// This class contains the logic to parse the AdditionalOptions project
/// property of v7.x J# projects and add the individual options as project
/// properties of the upgraded projects.
///
/// AdditionalOptions project property in v7.x was basically a string that
/// was passed ditto to the compiler.
/// It was used to hold J# compiler options that didnt have an 1-1 equivalent
/// project property.
/// For v8.0 and beyond, each J# compiler option has a corresponding project
/// property.
///
/// AdditionalOptions property string is broken down into list of options.
/// White space (only ' ' and '\t') are considered as delimiters if not wrapped
/// inside double quotes (").
/// NOTE:
/// 1. Other unicode spaces or double quotes sequences not considered
/// 2. Backslash (\) not considered as possible escape char for ".
///
/// Once broken down into individual options, only a few compiler options are
/// seached for (viz. the options for which v8.0 has new project properties)
/// Everything else is ignored.
///
/// Refer to SwitchesToMigrade enum for the switches that are migrated.
/// </summary>
internal sealed class AdditionalOptionsParser
{
// These are all that we recognize in the AdditionalOptions
private CompSwitchInfo[] validCompilerSwitches = new CompSwitchInfo[] {
#region Info on the compiler switches to be parsed from AdditionalOptions
// /codepage:<n>
new CompSwitchInfo(
SwitchesToMigrate.STM_CodePage,
new string[] { "/codepage:" },
SwitchValueType.SVT_String,
null,
"CodePage"
),
// /x:[all | net]
new CompSwitchInfo(
SwitchesToMigrate.STM_DisableLangExtensions,
new string[] { "/x:" },
SwitchValueType.SVT_String,
null,
"DisableLangXtns"
),
// /jcpa:[package=namespace | @filename]
new CompSwitchInfo(
SwitchesToMigrate.STM_Jcpa,
new string[] { "/jcpa:" },
SwitchValueType.SVT_MultiString,
new StringBuilder(),
"JCPA"
),
// /linkres[ource]:<resinfo>
new CompSwitchInfo(
SwitchesToMigrate.STM_LinkResource,
new string[] { "/linkres:", "/linkresource:" },
SwitchValueType.SVT_MultiString,
new StringBuilder(),
"LinkResource"
),
// /securescoping[+|-], /ss[+|-]
new CompSwitchInfo(
SwitchesToMigrate.STM_SecureScoping,
new string[] { "/securescoping", "/ss" },
SwitchValueType.SVT_Boolean,
null,
"SecureScoping"
),
// /win32res:<file>
new CompSwitchInfo(
SwitchesToMigrate.STM_Win32Resource,
new string[] { "/win32res:" },
SwitchValueType.SVT_String,
null,
"Win32Resource"
)
#endregion
};
/// <summary>
/// One and only entry point to the functionality offered by this class
/// </summary>
public void ProcessAdditionalOptions(
string additionalOptionsValue,
ProjectPropertyGroupElement configPropertyGroup
)
{
// Trivial case
if (additionalOptionsValue == null)
{
return;
}
// Tokenize the additional options first
string[] compSwitchList = TokenizeAdditionalOptionsValue(additionalOptionsValue);
// Extract the switch arguments
foreach (string compSwitch in compSwitchList)
{
foreach (CompSwitchInfo compSwitchInfo in validCompilerSwitches)
{
if (ExtractSwitchInfo(compSwitchInfo, compSwitch))
{
break;
}
}
}
// Finally populate the project file and we'r done!
PopulatePropertyGroup(configPropertyGroup);
}
/// <summary>
/// This will tokenize the given string using ' ' and '\t' as delimiters
/// The delimiters are escaped inside a pair of quotes
/// If there is an unbalanced quote, EOL is treated as the closing quotes
/// </summary>
private string[] TokenizeAdditionalOptionsValue(string additionalOptionsValue)
{
ArrayList tokens = new ArrayList();
bool inQuotes = false;
StringBuilder option = new StringBuilder();
foreach (char c in additionalOptionsValue)
{
switch (c)
{
case '\t':
case ' ':
if (inQuotes)
{
option.Append(c);
}
else
{
if (0 != option.Length)
{
tokens.Add(option.ToString());
option.Length = 0;
}
}
break;
case '"':
inQuotes = !inQuotes;
break;
default:
option.Append(c);
break;
}
}
// Ignore everything unbalanced quotes
if (!inQuotes)
{
tokens.Add(option.ToString());
}
return (string[])tokens.ToArray(typeof(string));
}
/// <summary>
/// If compSwitch is the compSwitchInfo compiler switch, then extract the switch args
/// Return
/// - true: if this is the switch (even if the switch args have error)
/// - false: this is not the switch
/// </summary>
private bool ExtractSwitchInfo(CompSwitchInfo compSwitchInfo, string compSwitch)
{
string matchedID = null;
// First see if we have a match...
for (int i = 0; i < compSwitchInfo.SwitchIDs.Length; i++)
{
if (compSwitch.StartsWith(compSwitchInfo.SwitchIDs[i], StringComparison.Ordinal))
{
matchedID = compSwitchInfo.SwitchIDs[i];
break;
}
}
// No no... we arent dealing with the correct switchInfo
if (matchedID == null)
{
return false;
}
// Now we can get to extracting the switch arguments
object switchVal = null;
switch (compSwitchInfo.SwitchValueType)
{
case SwitchValueType.SVT_Boolean:
if (matchedID.Length == compSwitch.Length)
{
switchVal = true;
}
else if ((matchedID.Length + 1) == compSwitch.Length)
{
if ('+' == compSwitch[matchedID.Length])
{
switchVal = true;
}
else if ('-' == compSwitch[matchedID.Length])
{
switchVal = false;
}
}
if (switchVal != null)
{
compSwitchInfo.SwitchValue = switchVal;
}
else
{
Debug.Assert(false, "Cannot parse boolean switch: " + compSwitch);
}
break;
case SwitchValueType.SVT_String:
if (matchedID.Length < compSwitch.Length)
{
switchVal = compSwitch.Substring(matchedID.Length);
}
if (switchVal != null)
{
compSwitchInfo.SwitchValue = switchVal;
}
else
{
Debug.Assert(false, "Cannot parse string switch: " + compSwitch);
}
break;
case SwitchValueType.SVT_MultiString:
Debug.Assert(
compSwitchInfo.SwitchValue != null,
"Non null switch value expected for a multistring switch: " + matchedID
);
if (matchedID.Length < compSwitch.Length)
{
switchVal = compSwitch.Substring(matchedID.Length);
}
if (switchVal != null)
{
((StringBuilder)(compSwitchInfo.SwitchValue)).Append(switchVal);
((StringBuilder)(compSwitchInfo.SwitchValue)).Append(";");
}
else
{
Debug.Assert(false, "Cannot parse multistring switch: " + compSwitch);
}
break;
default:
Debug.Assert(false, "Unknown switch value type");
break;
}
return true;
}
/// <summary>
/// Populate the property group with the individual options
/// </summary>
private void PopulatePropertyGroup(ProjectPropertyGroupElement configPropertyGroup)
{
string propertyName;
foreach (CompSwitchInfo compSwitchInfo in validCompilerSwitches)
{
propertyName = compSwitchInfo.SwitchProjectPropertyName;
// No need to remove the already existing property node
// since the switches we are dealing with couldnt have been
// set anywhere else in the property pages except the additional
// options
switch (compSwitchInfo.SwitchValueType)
{
case SwitchValueType.SVT_Boolean:
if (compSwitchInfo.SwitchValue != null)
{
configPropertyGroup.AddProperty(
propertyName,
compSwitchInfo.SwitchValue.ToString().ToLower(CultureInfo.InvariantCulture)
);
}
break;
case SwitchValueType.SVT_String:
if (compSwitchInfo.SwitchValue != null)
{
configPropertyGroup.AddProperty(
propertyName,
compSwitchInfo.SwitchValue.ToString()
);
}
break;
case SwitchValueType.SVT_MultiString:
Debug.Assert(compSwitchInfo.SwitchValue != null, "Expected non null value for multistring switch");
if (0 != ((StringBuilder)(compSwitchInfo.SwitchValue)).Length)
{
configPropertyGroup.AddProperty(
propertyName,
compSwitchInfo.SwitchValue.ToString()
);
}
break;
default:
Debug.Assert(false, "Unknown switch value type");
break;
}
}
}
}
}
|