com.unity.ide.visualstudio@2.0.8

## [2.0.8] - 2021-04-09

Project generation:

Improved generation performance (especially with DOTS enabled projects).
Improved stability.
Updated Analyzers lookup strategy.
Fixed .vsconfig file not generated when using "regenerate all".

Integration

Improved automation plugins.

Documentation

Open sourced automation plugins.
This commit is contained in:
Unity Technologies
2021-04-09 00:00:00 +00:00
parent 8cbbe811d0
commit 06b02acf9c
22 changed files with 9125 additions and 87 deletions

View File

@@ -1,10 +1,28 @@
# Code Editor Package for Visual Studio
## [2.0.8] - 2021-04-09
Project generation:
Improved generation performance (especially with DOTS enabled projects).
Improved stability.
Updated Analyzers lookup strategy.
Fixed .vsconfig file not generated when using "regenerate all".
Integration
Improved automation plugins.
Documentation
Open sourced automation plugins.
## [2.0.7] - 2021-02-02
Integration:
Remove com.unity.nuget.newtonsoft-json dependency in favor of the built-in JsonUtility for the VS Test Runner.
- Remove com.unity.nuget.newtonsoft-json dependency in favor of the built-in JsonUtility for the VS Test Runner.
## [2.0.6] - 2021-01-20

View File

@@ -0,0 +1,308 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
E08E02FF236392D000A4B1BE /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = E08E02FE236392D000A4B1BE /* main.mm */; };
E08E03022363933B00A4B1BE /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E08E03012363933B00A4B1BE /* AppKit.framework */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
E08E02F5236392A300A4B1BE /* AppleEventIntegration.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AppleEventIntegration.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
E08E02F8236392A300A4B1BE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
E08E02FE236392D000A4B1BE /* main.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = "<group>"; };
E08E03012363933B00A4B1BE /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
E08E02F2236392A300A4B1BE /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
E08E03022363933B00A4B1BE /* AppKit.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
E08E02EC236392A300A4B1BE = {
isa = PBXGroup;
children = (
E08E02F7236392A300A4B1BE /* AppleEventIntegration */,
E08E02F6236392A300A4B1BE /* Products */,
E08E03002363933B00A4B1BE /* Frameworks */,
);
sourceTree = "<group>";
};
E08E02F6236392A300A4B1BE /* Products */ = {
isa = PBXGroup;
children = (
E08E02F5236392A300A4B1BE /* AppleEventIntegration.bundle */,
);
name = Products;
sourceTree = "<group>";
};
E08E02F7236392A300A4B1BE /* AppleEventIntegration */ = {
isa = PBXGroup;
children = (
E08E02F8236392A300A4B1BE /* Info.plist */,
E08E02FE236392D000A4B1BE /* main.mm */,
);
path = AppleEventIntegration;
sourceTree = "<group>";
};
E08E03002363933B00A4B1BE /* Frameworks */ = {
isa = PBXGroup;
children = (
E08E03012363933B00A4B1BE /* AppKit.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
E08E02F4236392A300A4B1BE /* AppleEventIntegration */ = {
isa = PBXNativeTarget;
buildConfigurationList = E08E02FB236392A300A4B1BE /* Build configuration list for PBXNativeTarget "AppleEventIntegration" */;
buildPhases = (
E08E02F1236392A300A4B1BE /* Sources */,
E08E02F2236392A300A4B1BE /* Frameworks */,
E08E02F3236392A300A4B1BE /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = AppleEventIntegration;
productName = AppleEventIntegration;
productReference = E08E02F5236392A300A4B1BE /* AppleEventIntegration.bundle */;
productType = "com.apple.product-type.bundle";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
E08E02ED236392A300A4B1BE /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1200;
ORGANIZATIONNAME = Unity;
TargetAttributes = {
E08E02F4236392A300A4B1BE = {
CreatedOnToolsVersion = 11.1;
};
};
};
buildConfigurationList = E08E02F0236392A300A4B1BE /* Build configuration list for PBXProject "AppleEventIntegration" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = E08E02EC236392A300A4B1BE;
productRefGroup = E08E02F6236392A300A4B1BE /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
E08E02F4236392A300A4B1BE /* AppleEventIntegration */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
E08E02F3236392A300A4B1BE /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
E08E02F1236392A300A4B1BE /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E08E02FF236392D000A4B1BE /* main.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
E08E02F9236392A300A4B1BE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
};
name = Debug;
};
E08E02FA236392A300A4B1BE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu11;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.13;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
};
name = Release;
};
E08E02FC236392A300A4B1BE /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = AppleEventIntegration/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = com.unity.visualstudio.AppleEventIntegration;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
WRAPPER_EXTENSION = bundle;
};
name = Debug;
};
E08E02FD236392A300A4B1BE /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
DEVELOPMENT_TEAM = "";
INFOPLIST_FILE = AppleEventIntegration/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Bundles";
MACOSX_DEPLOYMENT_TARGET = 10.13;
PRODUCT_BUNDLE_IDENTIFIER = com.unity.visualstudio.AppleEventIntegration;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
WRAPPER_EXTENSION = bundle;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
E08E02F0236392A300A4B1BE /* Build configuration list for PBXProject "AppleEventIntegration" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E08E02F9236392A300A4B1BE /* Debug */,
E08E02FA236392A300A4B1BE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
E08E02FB236392A300A4B1BE /* Build configuration list for PBXNativeTarget "AppleEventIntegration" */ = {
isa = XCConfigurationList;
buildConfigurations = (
E08E02FC236392A300A4B1BE /* Debug */,
E08E02FD236392A300A4B1BE /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = E08E02ED236392A300A4B1BE /* Project object */;
}

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2019 Unity. All rights reserved.</string>
<key>NSPrincipalClass</key>
<string></string>
</dict>
</plist>

View File

@@ -0,0 +1,291 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Unity Technologies.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
// 'FSnd' FourCC
#define keyFileSender 1179872868
// 16 bit aligned legacy struct - this should total 20 bytes
struct SelectionRange
{
int16_t unused1; // 0 (not used)
int16_t lineNum; // line to select (<0 to specify range)
int32_t startRange; // start of selection range (if line < 0)
int32_t endRange; // end of selection range (if line < 0)
int32_t unused2; // 0 (not used)
int32_t theDate; // modification date/time
} __attribute__((packed));
static NSString* MakeNSString(const char* str)
{
if (!str)
return NULL;
NSString* ret = [NSString stringWithUTF8String: str];
return ret;
}
static UInt32 GetCreatorOfThisApp()
{
static UInt32 creator = 0;
if (creator == 0)
{
UInt32 type;
CFBundleGetPackageInfo(CFBundleGetMainBundle(), &type, &creator);
}
return creator;
}
static BOOL OpenFileAtLineWithAppleEvent(NSRunningApplication *runningApp, NSString* path, int line)
{
if (!runningApp)
return NO;
NSURL *pathUrl = [NSURL fileURLWithPath: path];
NSAppleEventDescriptor* targetDescriptor = [NSAppleEventDescriptor
descriptorWithProcessIdentifier: runningApp.processIdentifier];
NSAppleEventDescriptor* appleEvent = [NSAppleEventDescriptor
appleEventWithEventClass: kCoreEventClass
eventID: kAEOpenDocuments
targetDescriptor: targetDescriptor
returnID: kAutoGenerateReturnID
transactionID: kAnyTransactionID];
[appleEvent
setParamDescriptor: [NSAppleEventDescriptor
descriptorWithDescriptorType: typeFileURL
data: [[pathUrl absoluteString] dataUsingEncoding: NSUTF8StringEncoding]]
forKeyword: keyDirectObject];
UInt32 packageCreator = GetCreatorOfThisApp();
if (packageCreator == kUnknownType) {
[appleEvent
setParamDescriptor: [NSAppleEventDescriptor
descriptorWithDescriptorType: typeApplicationBundleID
data: [[[NSBundle mainBundle] bundleIdentifier] dataUsingEncoding: NSUTF8StringEncoding]]
forKeyword: keyFileSender];
} else {
[appleEvent
setParamDescriptor: [NSAppleEventDescriptor descriptorWithTypeCode: packageCreator]
forKeyword: keyFileSender];
}
if (line != -1) {
// Add selection range to event
SelectionRange range;
range.unused1 = 0;
range.lineNum = line - 1;
range.startRange = -1;
range.endRange = -1;
range.unused2 = 0;
range.theDate = -1;
[appleEvent
setParamDescriptor: [NSAppleEventDescriptor
descriptorWithDescriptorType: typeChar
bytes: &range
length: sizeof(SelectionRange)]
forKeyword: keyAEPosition];
}
AEDesc reply = { typeNull, NULL };
OSErr err = AESendMessage(
[appleEvent aeDesc],
&reply,
kAENoReply + kAENeverInteract,
kAEDefaultTimeout);
return err == noErr;
}
static BOOL ApplicationSupportsQueryOpenedSolution(NSString* appPath)
{
NSURL* appUrl = [NSURL fileURLWithPath: appPath];
NSBundle* bundle = [NSBundle bundleWithURL: appUrl];
if (!bundle)
return NO;
id versionValue = [bundle objectForInfoDictionaryKey: @"CFBundleVersion"];
if (!versionValue || ![versionValue isKindOfClass: [NSString class]])
return NO;
NSString* version = (NSString*)versionValue;
NSArray* components = [version componentsSeparatedByString:@"."];
if (!components || components.count < 2)
return NO;
return [components[0] integerValue] >= 8
&& [components[1] integerValue] >= 6;
}
static NSArray<NSRunningApplication*>* QueryRunningInstances(NSString *appPath)
{
NSMutableArray<NSRunningApplication*>* instances = [[NSMutableArray alloc] init];
NSURL *appUrl = [NSURL fileURLWithPath: appPath];
for (NSRunningApplication *runningApp in NSWorkspace.sharedWorkspace.runningApplications) {
if (![runningApp isTerminated] && [runningApp.bundleURL isEqual: appUrl]) {
[instances addObject: runningApp];
}
}
return instances;
}
enum {
kWorkspaceEventClass = 1448302419, /* 'VSWS' FourCC */
kCurrentSelectedSolutionPathEventID = 1129534288 /* 'CSSP' FourCC */
};
static BOOL TryQueryCurrentSolutionPath(NSRunningApplication* runningApp, NSString** solutionPath)
{
NSAppleEventDescriptor* targetDescriptor = [NSAppleEventDescriptor
descriptorWithProcessIdentifier: runningApp.processIdentifier];
NSAppleEventDescriptor* appleEvent = [NSAppleEventDescriptor
appleEventWithEventClass: kWorkspaceEventClass
eventID: kCurrentSelectedSolutionPathEventID
targetDescriptor: targetDescriptor
returnID: kAutoGenerateReturnID
transactionID: kAnyTransactionID];
AEDesc aeReply = { 0, };
OSErr sendResult = AESendMessage(
[appleEvent aeDesc],
&aeReply,
kAEWaitReply | kAENeverInteract,
kAEDefaultTimeout);
if (sendResult != noErr) {
return NO;
}
NSAppleEventDescriptor *reply = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy: &aeReply];
*solutionPath = [[reply descriptorForKeyword: keyDirectObject] stringValue];
return *solutionPath != NULL;
}
static NSRunningApplication* QueryRunningApplicationOpenedOnSolution(NSString* appPath, NSString* solutionPath)
{
BOOL supportsQueryOpenedSolution = ApplicationSupportsQueryOpenedSolution(appPath);
for (NSRunningApplication *runningApp in QueryRunningInstances(appPath)) {
// If the currently selected external editor does not support the opened solution apple event
// then fallback to the previous behavior: take the first opened VSM and open the solution
if (!supportsQueryOpenedSolution) {
OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1);
return runningApp;
}
NSString* currentSolutionPath;
if (TryQueryCurrentSolutionPath(runningApp, &currentSolutionPath)) {
if ([solutionPath isEqual:currentSolutionPath]) {
return runningApp;
}
} else {
// If VSM doesn't respond to the query opened solution event
// we fallback to the previous behavior too
OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1);
return runningApp;
}
}
return NULL;
}
static NSRunningApplication* LaunchApplicationOnSolution(NSString* appPath, NSString* solutionPath)
{
NSURL* appUrl = [NSURL fileURLWithPath: appPath];
NSMutableDictionary* config = [[NSMutableDictionary alloc] init];
NSRunningApplication* runningApp = [[NSWorkspace sharedWorkspace]
launchApplicationAtURL: appUrl
options: NSWorkspaceLaunchDefault | NSWorkspaceLaunchNewInstance
configuration: config
error: nil];
OpenFileAtLineWithAppleEvent(runningApp, solutionPath, -1);
return runningApp;
}
static NSRunningApplication* QueryOrLaunchApplication(NSString* appPath, NSString* solutionPath)
{
NSRunningApplication* runningApp = QueryRunningApplicationOpenedOnSolution(appPath, solutionPath);
if (!runningApp)
runningApp = LaunchApplicationOnSolution(appPath, solutionPath);
if (runningApp)
[runningApp activateWithOptions: 0];
return runningApp;
}
BOOL LaunchOrReuseApp(NSString* appPath, NSString* solutionPath, NSRunningApplication** outApp)
{
NSRunningApplication* app = QueryOrLaunchApplication(appPath, solutionPath);
if (outApp)
*outApp = app;
return app != NULL;
}
BOOL MonoDevelopOpenFile(NSString* appPath, NSString* solutionPath, NSString* filePath, int line)
{
NSRunningApplication* runningApp;
if (!LaunchOrReuseApp(appPath, solutionPath, &runningApp)) {
return FALSE;
}
if (filePath) {
return OpenFileAtLineWithAppleEvent(runningApp, filePath, line);
}
return YES;
}
#if BUILD_APP
int main(int argc, const char** argv)
{
if (argc != 5) {
printf("Usage: AppleEventIntegration appPath solutionPath filePath lineNumber\n");
return 1;
}
const char* appPath = argv[1];
const char* solutionPath = argv[2];
const char* filePath = argv[3];
const int lineNumber = atoi(argv[4]);
@autoreleasepool
{
MonoDevelopOpenFile(MakeNSString(appPath), MakeNSString(solutionPath), MakeNSString(filePath), lineNumber);
}
return 0;
}
#else
extern "C"
{
BOOL OpenVisualStudio(const char* appPath, const char* solutionPath, const char* filePath, int line)
{
return MonoDevelopOpenFile(MakeNSString(appPath), MakeNSString(solutionPath), MakeNSString(filePath), line);
}
}
#endif

View File

@@ -0,0 +1,40 @@
#pragma once
#include <OleAuto.h>
struct BStrHolder
{
BStrHolder() :
m_Str(NULL)
{
}
BStrHolder(const wchar_t* str) :
m_Str(SysAllocString(str))
{
}
~BStrHolder()
{
if (m_Str != NULL)
SysFreeString(m_Str);
}
operator BSTR() const
{
return m_Str;
}
BSTR* operator&()
{
if (m_Str != NULL)
{
SysFreeString(m_Str);
m_Str = NULL;
}
return &m_Str;
}
private:
BSTR m_Str;
};

View File

@@ -0,0 +1,26 @@
fileFormatVersion: 2
guid: 1f68874d6ae00db4a993b9507d065658
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.13)
project(com)
set(SOURCES
COMIntegration.cpp
BStrHolder.h
ComPtr.h
dte80a.tlh
)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -Wall")
add_executable(COMIntegration ${SOURCES})
target_link_libraries(COMIntegration Shlwapi.lib)

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 7cec3e1820a40be4486946c20d7ffd00
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,468 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Unity Technologies.
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
#include <iostream>
#include <sstream>
#include <string>
#include <filesystem>
#include <windows.h>
#include <shlwapi.h>
#include "BStrHolder.h"
#include "ComPtr.h"
#include "dte80a.tlh"
constexpr int RETRY_INTERVAL_MS = 150;
constexpr int TIMEOUT_MS = 10000;
// Often a DTE call made to Visual Studio can fail after Visual Studio has just started. Usually the
// return value will be RPC_E_CALL_REJECTED, meaning that Visual Studio is probably busy on another
// thread. This types filter the RPC messages and retries to send the message until VS accepts it.
class CRetryMessageFilter : public IMessageFilter
{
private:
static bool ShouldRetryCall(DWORD dwTickCount, DWORD dwRejectType)
{
if (dwRejectType == SERVERCALL_RETRYLATER || dwRejectType == SERVERCALL_REJECTED) {
return dwTickCount < TIMEOUT_MS;
}
return false;
}
win::ComPtr<IMessageFilter> currentFilter;
public:
CRetryMessageFilter()
{
HRESULT hr = CoRegisterMessageFilter(this, &currentFilter);
_ASSERT(SUCCEEDED(hr));
}
~CRetryMessageFilter()
{
win::ComPtr<IMessageFilter> messageFilter;
HRESULT hr = CoRegisterMessageFilter(currentFilter, &messageFilter);
_ASSERT(SUCCEEDED(hr));
}
// IUnknown methods
IFACEMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
static const QITAB qit[] =
{
QITABENT(CRetryMessageFilter, IMessageFilter),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
IFACEMETHODIMP_(ULONG) AddRef()
{
return 0;
}
IFACEMETHODIMP_(ULONG) Release()
{
return 0;
}
DWORD STDMETHODCALLTYPE HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo)
{
if (currentFilter)
return currentFilter->HandleInComingCall(dwCallType, htaskCaller, dwTickCount, lpInterfaceInfo);
return SERVERCALL_ISHANDLED;
}
DWORD STDMETHODCALLTYPE RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
{
if (ShouldRetryCall(dwTickCount, dwRejectType))
return RETRY_INTERVAL_MS;
if (currentFilter)
return currentFilter->RetryRejectedCall(htaskCallee, dwTickCount, dwRejectType);
return (DWORD)-1;
}
DWORD STDMETHODCALLTYPE MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
{
if (currentFilter)
return currentFilter->MessagePending(htaskCallee, dwTickCount, dwPendingType);
return PENDINGMSG_WAITDEFPROCESS;
}
};
static void DisplayProgressbar() {
std::wcout << "displayProgressBar" << std::endl;
}
static void ClearProgressbar() {
std::wcout << "clearprogressbar" << std::endl;
}
inline const std::wstring QuoteString(const std::wstring& str)
{
return L"\"" + str + L"\"";
}
static std::wstring ErrorCodeToMsg(DWORD code)
{
LPWSTR msgBuf = nullptr;
if (!FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, nullptr))
{
return L"Unknown error";
}
else
{
return msgBuf;
}
}
// Get an environment variable
static std::wstring GetEnvironmentVariableValue(const std::wstring& variableName) {
DWORD currentBufferSize = MAX_PATH;
std::wstring variableValue;
variableValue.resize(currentBufferSize);
DWORD requiredBufferSize = GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize);
if (requiredBufferSize == 0) {
// Environment variable probably does not exist.
return std::wstring();
}
if (currentBufferSize < requiredBufferSize) {
variableValue.resize(requiredBufferSize);
if (GetEnvironmentVariableW(variableName.c_str(), variableValue.data(), currentBufferSize) == 0)
return std::wstring();
}
variableValue.resize(requiredBufferSize);
return variableValue;
}
static bool StartVisualStudioProcess(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath,
DWORD *dwProcessId) {
STARTUPINFOW si;
PROCESS_INFORMATION pi;
BOOL result;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
std::wstring startingDirectory = visualStudioExecutablePath.parent_path();
// Build the command line that is passed as the argv of the VS process
// argv[0] must be the quoted full path to the VS exe
std::wstringstream commandLineStream;
commandLineStream << QuoteString(visualStudioExecutablePath) << L" ";
std::wstring vsArgsWide = GetEnvironmentVariableValue(L"UNITY_VS_ARGS");
if (!vsArgsWide.empty())
commandLineStream << vsArgsWide << L" ";
commandLineStream << QuoteString(solutionPath);
std::wstring commandLine = commandLineStream.str();
std::wcout << "Starting Visual Studio process with: " << commandLine << std::endl;
result = CreateProcessW(
visualStudioExecutablePath.c_str(), // Full path to VS, must not be quoted
commandLine.data(), // Command line, as passed as argv, separate arguments must be quoted if they contain spaces
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
false, // Set handle inheritance to FALSE
0, // No creation flags
nullptr, // Use parent's environment block
startingDirectory.c_str(), // starting directory set to the VS directory
&si,
&pi);
if (!result) {
DWORD error = GetLastError();
std::wcout << "Starting Visual Studio process failed: " << ErrorCodeToMsg(error) << std::endl;
return false;
}
*dwProcessId = pi.dwProcessId;
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return true;
}
static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithSolution(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath)
{
win::ComPtr<IUnknown> punk = nullptr;
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
CRetryMessageFilter retryMessageFilter;
// Search through the Running Object Table for an instance of Visual Studio
// to use that either has the correct solution already open or does not have
// any solution open.
win::ComPtr<IRunningObjectTable> ROT;
if (FAILED(GetRunningObjectTable(0, &ROT)))
return nullptr;
win::ComPtr<IBindCtx> bindCtx;
if (FAILED(CreateBindCtx(0, &bindCtx)))
return nullptr;
win::ComPtr<IEnumMoniker> enumMoniker;
if (FAILED(ROT->EnumRunning(&enumMoniker)))
return nullptr;
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (FAILED(ROT->GetObject(moniker, &punk)))
continue;
punk.As(&dte);
if (!dte)
continue;
// Okay, so we found an actual running instance of Visual Studio.
// Get the executable path of this running instance.
BStrHolder visualStudioFullName;
if (FAILED(dte->get_FullName(&visualStudioFullName)))
continue;
std::filesystem::path currentVisualStudioExecutablePath = std::wstring(visualStudioFullName);
// Ask for its current solution.
win::ComPtr<EnvDTE::_Solution> solution;
if (FAILED(dte->get_Solution(&solution)))
continue;
// Get the name of that solution.
BStrHolder solutionFullName;
if (FAILED(solution->get_FullName(&solutionFullName)))
continue;
std::filesystem::path currentSolutionPath = std::wstring(solutionFullName);
if (!currentSolutionPath.empty())
std::wcout << "Visual Studio opened on " << currentSolutionPath.wstring() << std::endl;
// If the name matches the solution we want to open and we have a Visual Studio installation path to use and this one matches that path, then use it.
// If we don't have a Visual Studio installation path to use, just use this solution.
if (std::filesystem::equivalent(currentSolutionPath, solutionPath)) {
std::wcout << "We found a running Visual Studio session with the solution open." << std::endl;
if (!visualStudioExecutablePath.empty()) {
if (std::filesystem::equivalent(currentVisualStudioExecutablePath, visualStudioExecutablePath)) {
return dte;
}
else {
std::wcout << "This running Visual Studio session does not seem to be the version requested in the user preferences. We will keep looking." << std::endl;
}
}
else {
std::wcout << "We're not sure which version of Visual Studio was requested in the user preferences. We will use this running session." << std::endl;
return dte;
}
}
}
return nullptr;
}
static bool
MonikerIsVisualStudioProcess(const win::ComPtr<IMoniker> &moniker, const win::ComPtr<IBindCtx> &bindCtx, const DWORD dwProcessId) {
LPOLESTR oleMonikerName;
if (FAILED(moniker->GetDisplayName(bindCtx, nullptr, &oleMonikerName)))
return false;
std::wstring monikerName(oleMonikerName);
// VisualStudio Moniker is "!VisualStudio.DTE.$Version:$PID"
// Example "!VisualStudio.DTE.14.0:1234"
if (monikerName.find(L"!VisualStudio.DTE") != 0)
return false;
std::wstringstream suffixStream;
suffixStream << ":";
suffixStream << dwProcessId;
std::wstring suffix(suffixStream.str());
return monikerName.length() - suffix.length() == monikerName.find(suffix);
}
static win::ComPtr<EnvDTE::_DTE> FindRunningVisualStudioWithPID(const DWORD dwProcessId) {
win::ComPtr<IUnknown> punk = nullptr;
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
// Search through the Running Object Table for a Visual Studio
// process with the process ID specified
win::ComPtr<IRunningObjectTable> ROT;
if (FAILED(GetRunningObjectTable(0, &ROT)))
return nullptr;
win::ComPtr<IBindCtx> bindCtx;
if (FAILED(CreateBindCtx(0, &bindCtx)))
return nullptr;
win::ComPtr<IEnumMoniker> enumMoniker;
if (FAILED(ROT->EnumRunning(&enumMoniker)))
return nullptr;
win::ComPtr<IMoniker> moniker;
ULONG monikersFetched = 0;
while (SUCCEEDED(enumMoniker->Next(1, &moniker, &monikersFetched)) && monikersFetched) {
if (FAILED(ROT->GetObject(moniker, &punk)))
continue;
if (!MonikerIsVisualStudioProcess(moniker, bindCtx, dwProcessId))
continue;
punk.As(&dte);
if (dte)
return dte;
}
return nullptr;
}
static bool HaveRunningVisualStudioOpenFile(const win::ComPtr<EnvDTE::_DTE> &dte, const std::filesystem::path &filename, int line) {
BStrHolder bstrFileName(filename.c_str());
BStrHolder bstrKind(L"{00000000-0000-0000-0000-000000000000}"); // EnvDTE::vsViewKindPrimary
win::ComPtr<EnvDTE::Window> window = nullptr;
CRetryMessageFilter retryMessageFilter;
if (!filename.empty()) {
std::wcout << "Getting operations API from the Visual Studio session." << std::endl;
win::ComPtr<EnvDTE::ItemOperations> item_ops;
if (FAILED(dte->get_ItemOperations(&item_ops)))
return false;
std::wcout << "Waiting for the Visual Studio session to open the file: " << filename.wstring() << "." << std::endl;
if (FAILED(item_ops->OpenFile(bstrFileName, bstrKind, &window)))
return false;
if (line > 0) {
win::ComPtr<IDispatch> selection_dispatch;
if (window && SUCCEEDED(window->get_Selection(&selection_dispatch))) {
win::ComPtr<EnvDTE::TextSelection> selection;
if (selection_dispatch &&
SUCCEEDED(selection_dispatch->QueryInterface(__uuidof(EnvDTE::TextSelection), &selection)) &&
selection) {
selection->GotoLine(line, false);
selection->EndOfLine(false);
}
}
}
}
window = nullptr;
if (SUCCEEDED(dte->get_MainWindow(&window))) {
// Allow the DTE to make its main window the foreground
HWND hWnd;
window->get_HWnd((LONG *)&hWnd);
DWORD processID;
if (SUCCEEDED(GetWindowThreadProcessId(hWnd, &processID)))
AllowSetForegroundWindow(processID);
// Activate() set the window to visible and active (blinks in taskbar)
window->Activate();
}
return true;
}
static bool VisualStudioOpenFile(
const std::filesystem::path &visualStudioExecutablePath,
const std::filesystem::path &solutionPath,
const std::filesystem::path &filename,
int line)
{
win::ComPtr<EnvDTE::_DTE> dte = nullptr;
std::wcout << "Looking for a running Visual Studio session." << std::endl;
// TODO: If path does not exist pass empty, which will just try to match all windows with solution
dte = FindRunningVisualStudioWithSolution(visualStudioExecutablePath, solutionPath);
if (!dte) {
std::wcout << "No appropriate running Visual Studio session not found, creating a new one." << std::endl;
DisplayProgressbar();
DWORD dwProcessId;
if (!StartVisualStudioProcess(visualStudioExecutablePath, solutionPath, &dwProcessId)) {
ClearProgressbar();
return false;
}
int timeWaited = 0;
while (timeWaited < TIMEOUT_MS) {
dte = FindRunningVisualStudioWithPID(dwProcessId);
if (dte)
break;
std::wcout << "Retrying to acquire DTE" << std::endl;
Sleep(RETRY_INTERVAL_MS);
timeWaited += RETRY_INTERVAL_MS;
}
ClearProgressbar();
if (!dte)
return false;
}
else {
std::wcout << "Using the existing Visual Studio session." << std::endl;
}
return HaveRunningVisualStudioOpenFile(dte, filename, line);
}
int wmain(int argc, wchar_t* argv[]) {
if (argc != 3 && argc != 5) {
std::wcerr << argc << ": wrong number of arguments\n" << "Usage: com.exe installationPath solutionPath [fileName lineNumber]" << std::endl;
for (int i = 0; i < argc; i++) {
std::wcerr << argv[i] << std::endl;
}
return EXIT_FAILURE;
}
if (FAILED(CoInitialize(nullptr))) {
std::wcerr << "CoInitialize failed." << std::endl;
return EXIT_FAILURE;
}
std::filesystem::path visualStudioExecutablePath = std::filesystem::absolute(argv[1]);
std::filesystem::path solutionPath = std::filesystem::absolute(argv[2]);
if (argc == 3) {
VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, L"", -1);
return EXIT_SUCCESS;
}
std::filesystem::path fileName = std::filesystem::absolute(argv[3]);
int lineNumber = std::stoi(argv[4]);
VisualStudioOpenFile(visualStudioExecutablePath, solutionPath, fileName, lineNumber);
return EXIT_SUCCESS;
}

View File

@@ -0,0 +1,27 @@
fileFormatVersion: 2
guid: 6ffa4010724f8d54aacbed867d4a5aa6
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
validateReferences: 1
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,186 @@
#pragma once
namespace win
{
template<typename T>
class ComPtr;
template<typename T>
class ComPtrRef
{
private:
ComPtr<T>& m_ComPtr;
ComPtrRef(ComPtr<T>& comPtr) :
m_ComPtr(comPtr)
{
}
friend class ComPtr<T>;
public:
inline operator T**()
{
return m_ComPtr.ReleaseAndGetAddressOf();
}
inline operator void**()
{
return reinterpret_cast<void**>(m_ComPtr.ReleaseAndGetAddressOf());
}
inline T* operator*() throw ()
{
return m_ComPtr;
}
};
template<typename T>
class ComPtr
{
private:
T *ptr;
public:
inline ComPtr(void) : ptr(NULL) {}
inline ~ComPtr(void) { this->Free(); }
ComPtr(T *ptr)
{
if (NULL != (this->ptr = ptr))
{
this->ptr->AddRef();
}
}
ComPtr(const ComPtr &ptr)
{
if (NULL != (this->ptr = ptr.ptr))
{
this->ptr->AddRef();
}
}
inline bool operator!() const
{
return (NULL == this->ptr);
}
inline operator T*() const { return this->ptr; }
inline T *operator->() const
{
//_assert(NULL != this->ptr);
return this->ptr;
}
inline T &operator*()
{
//_assert(NULL != this->ptr);
return *this->ptr;
}
inline ComPtrRef<T> operator&()
{
return ComPtrRef<T>(*this);
}
const ComPtr &operator=(T *ptr)
{
if (this->ptr != ptr)
{
this->Free();
if (NULL != (this->ptr = ptr))
{
this->ptr->AddRef();
}
}
return *this;
}
const ComPtr &operator=(const ComPtr &ptr)
{
if (this->ptr != ptr.ptr)
{
this->Free();
if (NULL != (this->ptr = ptr.ptr))
{
this->ptr->AddRef();
}
}
return *this;
}
void Free(void)
{
if (NULL != this->ptr)
{
this->ptr->Release();
this->ptr = NULL;
}
}
inline T** ReleaseAndGetAddressOf()
{
Free();
return &ptr;
}
template<typename U>
inline HRESULT As(ComPtrRef<U> p) const throw ()
{
return ptr->QueryInterface(__uuidof(U), p);
}
inline bool operator==(std::nullptr_t) const
{
return this->ptr == nullptr;
}
template<typename U>
inline bool operator==(U* other)
{
if (ptr == nullptr || other == nullptr)
return ptr == other;
ComPtr<IUnknown> meUnknown;
ComPtr<IUnknown> otherUnknown;
if (FAILED(this->ptr->QueryInterface(__uuidof(IUnknown), &meUnknown)))
return false;
if (FAILED(other->QueryInterface(__uuidof(IUnknown), &otherUnknown)))
return false;
return static_cast<IUnknown*>(meUnknown) == static_cast<IUnknown*>(otherUnknown);
}
template<typename U>
inline bool operator==(ComPtr<U>& other)
{
return *this == static_cast<U*>(other);
}
inline bool operator!=(std::nullptr_t) const
{
return this->ptr != nullptr;
}
template<typename U>
inline bool operator!=(U* other)
{
return !(*this == other);
}
template<typename U>
inline bool operator!=(ComPtr<U>& other)
{
return *this != static_cast<U*>(other);
}
};
}

View File

@@ -0,0 +1,26 @@
fileFormatVersion: 2
guid: 013868b12dff0dc43adcc33513ae71bf
PluginImporter:
externalObjects: {}
serializedVersion: 2
iconMap: {}
executionOrder: {}
defineConstraints: []
isPreloaded: 0
isOverridable: 1
isExplicitlyReferenced: 0
platformData:
- first:
Any:
second:
enabled: 1
settings: {}
- first:
Editor: Editor
second:
enabled: 0
settings:
DefaultValueInitialized: true
userData:
assetBundleName:
assetBundleVariant:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 97115cd910ade104a9d05d65a6b6b7d9
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1 @@
cl /EHsc /std:c++17 COMIntegration.cpp /link Shlwapi.lib /out:"..\Release\COMIntegration.exe"

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3b44687349be79f4184ba013fb6ffa0c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -30,6 +30,8 @@ namespace Microsoft.Unity.VisualStudio.Editor
public class AssemblyNameProvider : IAssemblyNameProvider
{
private readonly Dictionary<string, UnityEditor.PackageManager.PackageInfo> m_PackageInfoCache = new Dictionary<string, UnityEditor.PackageManager.PackageInfo>();
ProjectGenerationFlag m_ProjectGenerationFlag = (ProjectGenerationFlag)EditorPrefs.GetInt(
"unity_project_generation_flag",
(int)(ProjectGenerationFlag.Local | ProjectGenerationFlag.Embedded));
@@ -55,55 +57,35 @@ namespace Microsoft.Unity.VisualStudio.Editor
public IEnumerable<Assembly> GetAssemblies(Func<string, bool> shouldFileBePartOfSolution)
{
foreach (var assembly in CompilationPipeline.GetAssemblies())
IEnumerable<Assembly> assemblies = GetAssembliesByType(AssembliesType.Editor, shouldFileBePartOfSolution, @"Temp\Bin\Debug\");
if (!ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.PlayerAssemblies))
{
return assemblies;
}
var playerAssemblies = GetAssembliesByType(AssembliesType.Player, shouldFileBePartOfSolution, @"Temp\Bin\Debug\Player\");
return assemblies.Concat(playerAssemblies);
}
private static IEnumerable<Assembly> GetAssembliesByType(AssembliesType type, Func<string, bool> shouldFileBePartOfSolution, string outputPath)
{
foreach (var assembly in CompilationPipeline.GetAssemblies(type))
{
if (assembly.sourceFiles.Any(shouldFileBePartOfSolution))
{
var options = new ScriptCompilerOptions
{
ResponseFiles = assembly.compilerOptions.ResponseFiles,
AllowUnsafeCode = assembly.compilerOptions.AllowUnsafeCode,
ApiCompatibilityLevel = assembly.compilerOptions.ApiCompatibilityLevel
};
yield return new Assembly(assembly.name, @"Temp\Bin\Debug\",
assembly.sourceFiles, new[] { "DEBUG", "TRACE" }.Concat(assembly.defines).Concat(EditorUserBuildSettings.activeScriptCompilationDefines).ToArray(),
yield return new Assembly(
assembly.name,
outputPath,
assembly.sourceFiles,
assembly.defines,
assembly.assemblyReferences,
assembly.compiledAssemblyReferences,
assembly.flags,
assembly.compilerOptions
#if UNITY_2020_2_OR_NEWER
options,
assembly.rootNamespace);
#else
options);
#endif
}
}
if (ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.PlayerAssemblies))
{
foreach (var assembly in CompilationPipeline.GetAssemblies(AssembliesType.Player).Where(assembly => assembly.sourceFiles.Any(shouldFileBePartOfSolution)))
{
var options = new ScriptCompilerOptions
{
ResponseFiles = assembly.compilerOptions.ResponseFiles,
AllowUnsafeCode = assembly.compilerOptions.AllowUnsafeCode,
ApiCompatibilityLevel = assembly.compilerOptions.ApiCompatibilityLevel
};
yield return
new Assembly(assembly.name, @"Temp\Bin\Debug\Player\",
assembly.sourceFiles,
new[] { "DEBUG", "TRACE" }.Concat(assembly.defines).ToArray(),
assembly.assemblyReferences,
assembly.compiledAssemblyReferences,
assembly.flags,
#if UNITY_2020_2_OR_NEWER
options,
assembly.rootNamespace);
#else
options);
, assembly.rootNamespace
#endif
);
}
}
}
@@ -118,9 +100,39 @@ namespace Microsoft.Unity.VisualStudio.Editor
return AssetDatabase.GetAllAssetPaths();
}
private static string ResolvePotentialParentPackageAssetPath(string assetPath)
{
const string packagesPrefix = "packages/";
if (!assetPath.StartsWith(packagesPrefix, StringComparison.OrdinalIgnoreCase))
{
return null;
}
var followupSeparator = assetPath.IndexOf('/', packagesPrefix.Length);
if (followupSeparator == -1)
{
return assetPath.ToLowerInvariant();
}
return assetPath.Substring(0, followupSeparator).ToLowerInvariant();
}
public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath)
{
return UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath);
var parentPackageAssetPath = ResolvePotentialParentPackageAssetPath(assetPath);
if (parentPackageAssetPath == null)
{
return null;
}
if (m_PackageInfoCache.TryGetValue(parentPackageAssetPath, out var cachedPackageInfo))
{
return cachedPackageInfo;
}
var result = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(parentPackageAssetPath);
m_PackageInfoCache[parentPackageAssetPath] = result;
return result;
}
public bool IsInternalizedPackagePath(string path)
@@ -177,6 +189,11 @@ namespace Microsoft.Unity.VisualStudio.Editor
}
}
internal void ResetPackageInfoCache()
{
m_PackageInfoCache.Clear();
}
public void ResetProjectGenerationFlag()
{
ProjectGenerationFlag = ProjectGenerationFlag.None;

View File

@@ -104,10 +104,6 @@ namespace Microsoft.Unity.VisualStudio.Editor
{
SetupProjectSupportedExtensions();
// See https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/
// We create a .vsconfig file to make sure our ManagedGame workload is installed
CreateVsConfigIfNotFound();
// Don't sync if we haven't synced before
if (HasSolutionBeenGenerated() && HasFilesBeenModified(affectedFiles, reimportedFiles))
{
@@ -161,6 +157,13 @@ namespace Microsoft.Unity.VisualStudio.Editor
RefreshCurrentInstallation();
SetupProjectSupportedExtensions();
(m_AssemblyNameProvider as AssemblyNameProvider)?.ResetPackageInfoCache();
// See https://devblogs.microsoft.com/setup/configure-visual-studio-across-your-organization-with-vsconfig/
// We create a .vsconfig file to make sure our ManagedGame workload is installed
CreateVsConfigIfNotFound();
var externalCodeAlreadyGeneratedProjects = OnPreGeneratingCSProjectFiles();
if (!externalCodeAlreadyGeneratedProjects)
@@ -322,7 +325,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
stringBuilders[assemblyName] = projectBuilder;
}
projectBuilder.Append(" <None Include=\"").Append(EscapedRelativePathFor(asset)).Append("\" />").Append(k_WindowsNewline);
projectBuilder.Append(" <None Include=\"").Append(EscapedRelativePathFor(asset)).Append("\" />").Append(k_WindowsNewline);
}
}
@@ -367,6 +370,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
{
return TypeCache
.GetTypesDerivedFrom<AssetPostprocessor>()
.Where(t => t.Assembly.GetName().Name != "SyntaxTree.VisualStudio.Unity.Bridge") // never call into the bridge if loaded with the package
.Select(t => t.GetMethod(name, SR.BindingFlags.Public | SR.BindingFlags.NonPublic | SR.BindingFlags.Static))
.Where(m => m != null);
}
@@ -466,12 +470,19 @@ namespace Microsoft.Unity.VisualStudio.Editor
}
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
// Append additional non-script files that should be included in project generation.
if (allAssetsProjectParts.TryGetValue(assembly.name, out var additionalAssetsForProject))
{
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
projectBuilder.Append(additionalAssetsForProject);
projectBuilder.Append(@" </ItemGroup>").Append(k_WindowsNewline);
}
projectBuilder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r));
var internalAssemblyReferences = assembly.assemblyReferences
.Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath);

View File

@@ -308,7 +308,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
StartInfo = new ProcessStartInfo
{
FileName = progpath,
Arguments = $"\"{CodeEditor.CurrentEditorInstallation}\" \"{absolutePath}\" {solution} {line}",
Arguments = $"\"{CodeEditor.CurrentEditorInstallation}\" {solution} \"{absolutePath}\" {line}",
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,

View File

@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
using System;
using System.IO;
using System.Linq;
using Microsoft.Win32;
using Unity.CodeEditor;
using IOPath = System.IO.Path;
@@ -78,7 +77,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
if (versions != null)
{
foreach(var entry in versions)
foreach (var entry in versions)
{
if (Version >= entry.IdeVersion)
return entry.LanguageVersion;
@@ -86,7 +85,7 @@ namespace Microsoft.Unity.VisualStudio.Editor
}
// default to 7.0 given we support at least VS 2017
return new Version(7,0);
return new Version(7, 0);
}
}
@@ -105,31 +104,37 @@ namespace Microsoft.Unity.VisualStudio.Editor
}
}
private string GetWindowsBridgeFromRegistry()
{
var keyName = $"Software\\Microsoft\\Microsoft Visual Studio {Version.Major}.0 Tools for Unity";
const string valueName = "UnityExtensionPath";
var bridge = ReadRegistry(Registry.CurrentUser, keyName, valueName);
if (string.IsNullOrEmpty(bridge))
bridge = ReadRegistry(Registry.LocalMachine, keyName, valueName);
return bridge;
}
// We only use this to find analyzers, we do not need to load this assembly anymore
private string GetBridgeLocation()
private string GetExtensionPath()
{
if (VisualStudioEditor.IsWindows)
{
// Registry, using legacy bridge location
var keyName = $"Software\\Microsoft\\Microsoft Visual Studio {Version.Major}.0 Tools for Unity";
const string valueName = "UnityExtensionPath";
const string extensionName = "Visual Studio Tools for Unity";
const string extensionAssembly = "SyntaxTree.VisualStudio.Unity.dll";
var bridge = ReadRegistry(Registry.CurrentUser, keyName, valueName);
if (string.IsNullOrEmpty(bridge))
bridge = ReadRegistry(Registry.LocalMachine, keyName, valueName);
var vsDirectory = IOPath.GetDirectoryName(Path);
var vstuDirectory = IOPath.Combine(vsDirectory, "Extensions", "Microsoft", extensionName);
return bridge;
if (File.Exists(IOPath.Combine(vstuDirectory, extensionAssembly)))
return vstuDirectory;
}
if (VisualStudioEditor.IsOSX)
{
// Environment, useful when developing UnityVS for Mac
var bridge = Environment.GetEnvironmentVariable("VSTUM_BRIDGE");
if (!string.IsNullOrEmpty(bridge) && File.Exists(bridge))
return bridge;
const string addinBridge = "Editor/SyntaxTree.VisualStudio.Unity.Bridge.dll";
const string addinName = "MonoDevelop.Unity";
const string addinAssembly = addinName + ".dll";
// user addins repository
var localAddins = IOPath.Combine(
@@ -138,38 +143,54 @@ namespace Microsoft.Unity.VisualStudio.Editor
// In the user addins repository, the addins are suffixed by their versions, like `MonoDevelop.Unity.1.0`
// When installing another local user addin, MD will remove files inside the folder
// So we browse all VSTUM addins, and return the one with a bridge, which is the one MD will load
// So we browse all VSTUM addins, and return the one with an addin assembly
if (Directory.Exists(localAddins))
{
foreach (var folder in Directory.GetDirectories(localAddins, addinName + "*", SearchOption.TopDirectoryOnly))
{
bridge = IOPath.Combine(folder, addinBridge);
if (File.Exists(bridge))
return bridge;
if (File.Exists(IOPath.Combine(folder, addinAssembly)))
return folder;
}
}
// Check in Visual Studio.app/
// In that case the name of the addin is used
bridge = IOPath.Combine(Path, $"Contents/Resources/lib/monodevelop/AddIns/{addinName}/{addinBridge}");
if (File.Exists(bridge))
return bridge;
var addinPath = IOPath.Combine(Path, $"Contents/Resources/lib/monodevelop/AddIns/{addinName}");
if (File.Exists(IOPath.Combine(addinPath, addinAssembly)))
return addinPath;
}
return null;
}
private static string[] GetAnalyzers(string path)
{
var analyzersDirectory = IOPath.GetFullPath(IOPath.Combine(path, "Analyzers"));
if (Directory.Exists(analyzersDirectory))
return Directory.GetFiles(analyzersDirectory, "*Analyzers.dll", SearchOption.AllDirectories);
return Array.Empty<string>();
}
public string[] GetAnalyzers()
{
var bridge = GetBridgeLocation();
var vstuPath = GetExtensionPath();
if (string.IsNullOrEmpty(vstuPath))
return Array.Empty<string>();
if (!string.IsNullOrEmpty(bridge))
if (VisualStudioEditor.IsOSX)
return GetAnalyzers(vstuPath);
if (VisualStudioEditor.IsWindows)
{
var baseLocation = IOPath.Combine(IOPath.GetDirectoryName(bridge), "..");
var analyzerLocation = IOPath.GetFullPath(IOPath.Combine(baseLocation, "Analyzers"));
var analyzers = GetAnalyzers(vstuPath);
if (analyzers?.Length > 0)
return analyzers;
if (Directory.Exists(analyzerLocation))
return Directory.GetFiles(analyzerLocation, "*Analyzers.dll", SearchOption.AllDirectories);
var bridge = GetWindowsBridgeFromRegistry();
if (File.Exists(bridge))
return GetAnalyzers(IOPath.Combine(IOPath.GetDirectoryName(bridge), ".."));
}
// Local assets

View File

@@ -2,21 +2,21 @@
"name": "com.unity.ide.visualstudio",
"displayName": "Visual Studio Editor",
"description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.",
"version": "2.0.7",
"unity": "2020.1",
"unityRelease": "0a12",
"version": "2.0.8",
"unity": "2019.4",
"unityRelease": "21f1",
"dependencies": {
"com.unity.test-framework": "1.1.9"
},
"relatedPackages": {
"com.unity.ide.visualstudio.tests": "2.0.7"
"com.unity.ide.visualstudio.tests": "2.0.8"
},
"upmCi": {
"footprint": "b6515ac9d75224fe45e288270d26a9e031c550a8"
"footprint": "c51cc3db630d4f769464e8f12c2154af3ca5242c"
},
"repository": {
"url": "https://github.cds.internal.unity3d.com/unity/com.unity.ide.visualstudio.git",
"type": "git",
"revision": "dec282022c7a95fada560c36f53da9dd155a142c"
"revision": "5cdf5d932e6ecbbd83bbe6a40eec2a94b4979501"
}
}