Skip to content

[WIP] Adopt flag prep logic #7

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -4,4 +4,4 @@
build
*.DS_Store
!vendor/.gitignore

compile_commands.json
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -98,11 +98,11 @@ add_executable(http_server
HTTPServerMain.cpp
)

add_executable(test_driver
add_executable(tests
Logging.hpp
SwiftCompleter.hpp
SwiftCompleter.cpp
Driver.cpp
UnitTests.cpp
)

add_executable(integration_tests
71 changes: 0 additions & 71 deletions Driver.cpp

This file was deleted.

26 changes: 26 additions & 0 deletions Examples/iOS/Basic/Basic/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// AppDelegate.swift
// Basic
//
// Created by Jerry Marino on 5/13/17.
// Copyright © 2017 Jerry Marino. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

var window: UIWindow?


func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}

func ycmdMethod() {
self.window?.sub
}
}

24 changes: 24 additions & 0 deletions Examples/iOS/Basic/Basic/ViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// ViewController.swift
// Basic
//
// Created by Jerry Marino on 5/13/17.
// Copyright © 2017 Jerry Marino. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
let delegate = UIApplication.shared.delegate as! AppDelegate
// Here we are calling a custom method on the AppDelegate
delegate.ycmd
}
}

21 changes: 21 additions & 0 deletions Examples/iOS/Basic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# YCMD iOS Swift Example

This directory contains a basic example of an iOS project running under YCMD.

The example contains a project generated from Xcode, minus any unneeded files (
assets, xcodeproj, etc )

It's a simple iOS app and ViewController.swift depends on AppDelegate.swift.

It should serve as a base case so that we can make sure symbols are correctly
loading for people from the iOS SDK and external files.

These abilities are mostly dependent on a correct Compilation Database.

The Compilation Database is a template which is written in test setup. It must
be in the root directory of the examples. This template was generated from a
build of these files under xcodebuild, with the following manual changes:

- replace the source root with __SRCROOT__
- remove 10.3 version requirement
- strip out flags that pass missing build artifacts
12 changes: 12 additions & 0 deletions Examples/iOS/Basic/compile_commands.json.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[
{
"directory": "__SRCROOT__",
"command": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -c -primary-file __SRCROOT__/Basic/ViewController.swift __SRCROOT__/Basic/AppDelegate.swift -enable-objc-interop -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -enable-testing -g -serialize-debugging-options -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/swift-overrides.hmap -Xcc -iquote -Xcc /Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-generated-files.hmap -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-own-target-headers.hmap -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-all-target-headers.hmap -Xcc -iquote -Xcc /Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-project-headers.hmap -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Products/Debug-iphonesimulator/include -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/DerivedSources/x86_64 -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/DerivedSources -Xcc -DDEBUG=1 -Xcc -working-directory__SRCROOT__ -Onone -module-name Basic -o /Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Objects-normal/x86_64/ViewController.o",
"file": "__SRCROOT__/Basic/ViewController.swift"
},
{
"directory": "__SRCROOT__",
"command": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift -frontend -c __SRCROOT__/Basic/ViewController.swift -primary-file __SRCROOT__/Basic/AppDelegate.swift -enable-objc-interop -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk -enable-testing -g -serialize-debugging-options -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/swift-overrides.hmap -Xcc -iquote -Xcc /Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-generated-files.hmap -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-own-target-headers.hmap -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-all-target-headers.hmap -Xcc -iquote -Xcc /Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Basic-project-headers.hmap -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Products/Debug-iphonesimulator/include -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/DerivedSources/x86_64 -Xcc -I/Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/DerivedSources -Xcc -DDEBUG=1 -Xcc -working-directory__SRCROOT__ -Onone -module-name Basic -o /Users/fakeuser/Library/Developer/Xcode/DerivedData/Basic-dcvyhoqxsulylretnajwiqnkngaj/Build/Intermediates/Basic.build/Debug-iphonesimulator/Basic.build/Objects-normal/x86_64/AppDelegate.o",
"file": "__SRCROOT__/Basic/AppDelegate.swift"
}
]
93 changes: 91 additions & 2 deletions SwiftCompleter.cpp
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
#import <future>
#import <iostream>
#import <map>
#import <set>
#import <sourcekitd/sourcekitd.h>
#import <sstream>
#import <string>
@@ -449,7 +450,7 @@ const std::string SwiftCompleter::CandidatesForLocationInFile(
ctx.line = line;
ctx.column = column;
ctx.unsavedFiles = unsavedFiles;
ctx.flags = flags;
ctx.flags = FlagsForCompileCommand(flags);

SourceKitService sktService(_logger.level());
char *response = NULL;
@@ -458,14 +459,30 @@ const std::string SwiftCompleter::CandidatesForLocationInFile(
return response;
}

// Transform completion flags into diagnostic flags
auto DiagnosticFlagsFromFlags(std::string filename,
std::vector<std::string> flags) {
std::vector<std::string> outputFlags;
// Skip the file - I'm not 100% sure why we need this but it will output
// weird warnings if not.
for (auto &f : flags) {
if (f == filename) {
continue;
}
outputFlags.push_back(f);
}
return outputFlags;
}

const std::string
SwiftCompleter::DiagnosticsForFile(const std::string &filename,
const std::vector<UnsavedFile> &unsavedFiles,
const std::vector<std::string> &flags) {
CompletionContext ctx;
ctx.sourceFilename = filename;
ctx.unsavedFiles = unsavedFiles;
ctx.flags = flags;
auto completionFlags = FlagsForCompileCommand(flags);
ctx.flags = DiagnosticFlagsFromFlags(filename, completionFlags);
ctx.line = 0;
ctx.column = 0;

@@ -483,3 +500,75 @@ SwiftCompleter::DiagnosticsForFile(const std::string &filename,
return semaresult;
}
} // namespace ssvim
// namespace ssvim

#pragma mark - Command Preparation Logic

// Basic flag blacklist is a list of flags that cannot be included in a
// CompilerInvocation for completion.
//
// These flags are a pair in the form
// __FLAG__ Optional(__VALUE__)
//
// For example -c sometimes has a value after it.
//
// Only skip __VALUE__ when it doesn't start with -.

auto FlagStartToken = '-';
std::set<std::string> BasicFlagBlacklist{"-c",
"-MP",
"-MD",
"-MMD",
"--fcolor-diagnostics",
"-emit-reference-dependencies-path",
"-emit-dependencies-path",
"-emit-module-path",
"-serialize-diagnostics-path",
"-emit-module-doc-path",
"-frontend",
"-o"};

// These flags may specified as pair of the form
// __FLAG__ __VALUE__
//
// Unconditionally exclude flags in this blacklist and the next value

std::set<std::string> PairedFlagBlacklist{"-Xcc"};

// Take a raw split command and output completion flags
std::vector<std::string>
ssvim::FlagsForCompileCommand(std::vector<std::string> flags) {
if (flags.size() == 0) {
return flags;
}

std::vector<std::string> outFlags;

// Skip the first flag if needed
// Assume that someone will be using a swift compiler with an absolute path
// and strip it off if so. All compile commands should be formed this way,
// but some old test code uses it, so leave it for now.
auto isCompilerBinary = flags.at(0)[0] == '/';
unsigned long i = isCompilerBinary ? 1 : 0;
auto length = flags.size();
while (i < length) {
auto flag = flags.at(i);
if (PairedFlagBlacklist.find(flag) != PairedFlagBlacklist.end()) {
i = i + 1;
} else if (BasicFlagBlacklist.find(flag) != BasicFlagBlacklist.end()) {
auto nextIdx = i + 1;

// Skip the pair (FLAG, VALUE) when the next value isn't
// another flag.
if (nextIdx < length) {
if (flags[nextIdx][0] != FlagStartToken) {
i = i + 1;
}
}
} else {
outFlags.push_back(flag);
}
i = i + 1;
}
return outFlags;
}
6 changes: 6 additions & 0 deletions SwiftCompleter.hpp
Original file line number Diff line number Diff line change
@@ -39,4 +39,10 @@ class SwiftCompleter {
const std::vector<UnsavedFile> &unsavedFiles,
const std::vector<std::string> &flags);
};

#pragma mark - Testing

extern std::vector<std::string>
FlagsForCompileCommand(std::vector<std::string> flags);

} // namespace ssvim
213 changes: 213 additions & 0 deletions UnitTests.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
#import "Logging.hpp"
#import "SwiftCompleter.hpp"
#import <boost/algorithm/string/replace.hpp>
#import <boost/property_tree/json_parser.hpp>
#import <boost/property_tree/ptree.hpp>
#import <dispatch/dispatch.h>
#import <fstream>
#import <iostream>
#import <sstream>
#import <string>
#import <sys/socket.h>
#import <tuple>
#import <vector>

using namespace ssvim;
using namespace std;

std::string GetExamplesDir() {
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) != NULL) {
return std::string(std::string(cwd) + "/Examples/");
}
return "";
}

std::string ReadFile(const std::string &fileName) {
std::ifstream ifs(fileName.c_str(),
std::ios::in | std::ios::binary | std::ios::ate);

std::ifstream::pos_type fileSize = ifs.tellg();
ifs.seekg(0, std::ios::beg);

std::vector<char> bytes(fileSize);
ifs.read(&bytes[0], fileSize);
return std::string(&bytes[0], fileSize);
}
using boost::property_tree::ptree;
using boost::property_tree::read_json;
using boost::property_tree::write_json;

ptree ReadJSON(std::string body) {
ptree pt;
std::istringstream is(body);
read_json(is, pt);
return pt;
}

// Write out an example DB.
// Template the __SRCROOT__ with an absolute
// return the ptree value
ptree SetupTemplateCompilationDB() {
std::string exampleRoot = GetExamplesDir() + "iOS/Basic";
auto exampleTemplate =
exampleRoot + "/" + std::string("compile_commands.json.template");
std::ofstream outFile(
std::string(exampleRoot + "/" + std::string("compile_commands.json")));
std::ifstream readFile(exampleTemplate);
std::string line;
std::string outStr;
while (std::getline(readFile, line)) {
boost::replace_all(line, std::string("__SRCROOT__"), exampleRoot);
outFile << line << std::endl;
outStr = outStr + line + "\n";
}
outStr += "\0";
auto JSON = ReadJSON(outStr);
return JSON;
}

// Lex a shell invocation
template <class C = std::vector<std::string>> C shlex(std::string s) {
auto result = C{};

auto accumulator = std::string{};
auto quote = char{};
auto escape = bool{};

auto evictAccumulator = [&]() {
if (!accumulator.empty()) {
result.push_back(std::move(accumulator));
accumulator = "";
}
};

for (auto c : s) {
if (escape) {
escape = false;
accumulator += c;
} else if (c == '\\') {
escape = true;
} else if ((quote == '\0' && c == '\'') || (quote == '\0' && c == '\"')) {
quote = c;
} else if ((quote == '\'' && c == '\'') || (quote == '"' && c == '"')) {
quote = '\0';
} else if (!isspace(c) || quote != '\0') {
accumulator += c;
} else {
evictAccumulator();
}
}

evictAccumulator();

return result;
}

auto BasicExampleNamed(std::string name) {
std::string exampleRoot = GetExamplesDir() + "iOS/Basic";
return exampleRoot + "/" + name;
}

class FlagTestSuite {
public:
FlagTestSuite() {
testBasicFlags();
testDepFlags();
}

std::vector<std::string>
getExampleCompileCommandForFile(std::string testFile) {
auto compDB = SetupTemplateCompilationDB();
std::cout << testFile;
std::vector<std::string> command;
for (auto item : compDB) {
auto fileName = item.second.get<std::string>("file");
auto commandStr = item.second.get<std::string>("command");
if (fileName == testFile) {
command = shlex(commandStr);
}
}
assert(command.size() > 0);
return command;
}

void testBasicFlags() {
auto testFile = BasicExampleNamed("Basic/AppDelegate.swift");
auto command = getExampleCompileCommandForFile(testFile);
auto prepped = FlagsForCompileCommand(command);
assert(prepped.at(0) == "-primary-file");
assert(prepped.at(1) == testFile);
}

void testDepFlags() {
auto testFile = BasicExampleNamed("Basic/ViewController.swift");
auto depFile = BasicExampleNamed("Basic/AppDelegate.swift");
auto command = getExampleCompileCommandForFile(testFile);
auto prepped = FlagsForCompileCommand(command);
assert(prepped.at(0) == "-primary-file");
assert(prepped.at(1) == testFile);
assert(prepped.at(2) == depFile);
}
};

using namespace ssvim;
static ssvim::Logger logger(LogLevelInfo);

struct Runner {
std::string complete(std::string fileName, std::string fileContents,
std::vector<std::string> flags, unsigned line,
unsigned column) {
auto completer = SwiftCompleter(LogLevelExtreme);
auto files = std::vector<UnsavedFile>();
auto unsavedFile = UnsavedFile();
unsavedFile.contents = fileContents;
unsavedFile.fileName = fileName;

files.push_back(unsavedFile);
auto result = completer.CandidatesForLocationInFile(fileName, line, column,
files, flags);

return result;
}
};

class CompleterSmokeTestSuite {
public:
CompleterSmokeTestSuite() {
logger << "Running Smoke tests";
Runner runner;
vector<string> flags;
flags.push_back("-sdk");
flags.push_back("/Applications/Xcode.app/Contents/Developer/Platforms/"
"MacOSX.platform/Developer/SDKs/MacOSX.sdk");
flags.push_back("-target");
flags.push_back("x86_64-apple-macosx10.12");

auto exampleDir = GetExamplesDir();
auto exampleName = exampleDir + std::string("some_swift.swift");
auto example = ReadFile(exampleName);
std::cout << example;
auto result = runner.complete(exampleName, example, flags, 19, 16);

// FIXME: parse this response.
// we should have a massive response here.
assert(result.size() > 500);
logger << "Ran Smoke tests";
}
};

int wrapped_main() {
FlagTestSuite suite;
// TODO: this should be probably moved out of here. We are doing
// assertions on code paths that we don't own
CompleterSmokeTestSuite smokeTest;
exit(0);
}

int main() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
wrapped_main();
});
dispatch_main();
}