Skip to content
This repository has been archived by the owner on Oct 4, 2019. It is now read-only.

There should be way to recompile files without clang proxying (Xcode) #24

Closed
PaulTaykalo opened this issue Jun 11, 2013 · 9 comments
Closed

Comments

@PaulTaykalo
Copy link
Member

There SHOULD be way.
Any suggestions are appreciated.

This should work for Xcode and AppCode For Any project.

@PaulTaykalo
Copy link
Member Author

this will fix the #8 issue, and will make dyci clean and clear

@rodionovd
Copy link

Hi, @PaulTaykalo. I've reverse-engineered Xcode and found a way to get a compiler/linker arguments for any source file in a project from inside your plugin.
TL;DR: a complete source code is in the end of this comment;

Compiler/linker arguments for a file

Firstly, you need to create a PBXReference for the file:

Class PBXReference = NSClassFromString(@"PBXReference");
id file_ref = [[PBXReference alloc] initWithPath: @"/full/path/to/source_file.m"];

Next, search for a targets contain this file (there may be more than one target so you should perform additional checks):

Class PBXProject = NSClassFromString(@"PBXProject");
id suggested_targets = [PBXProject performSelector: NSSelectorFromString(@"targetsInAllProjectsForFileReference:justNative:")
                                        withObject: file_ref
                                        withObject: nil];
// just a first one
id target = suggested_targets[0];

Once you have a target you can ask it for a «build context» — the thing contains all information required for building the target.

id build_context = [target performSelector: NSSelectorFromString(@"targetBuildContext")];

Each «build context» has a set of commands which must be executed upon building. In terms of Xcode they're called «dependency commands».
To define each load command we can use its ruleInfo field; it's just an array, first item of which is a title of the «dependency command»: CompileC, Ld, Touch, etc.

NSArray *commands = [build_context performSelector: NSSelectorFromString(@"commands")];
[commands enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{        
        NSArray *rule_info = [obj performSelector: NSSelectorFromString(@"ruleInfo")];
        // Dump compiler arguments for the file
        if ([[rule_info firstObject] isEqualToString: @"CompileC"]) {
            /**
             * «Dependency commands» can contain build rules for multiple files, so 
             * make sure you got the right one.
             */
            NSString *sourcefile_path = [rule_info objectAtIndex: 2];
            if ( ! [sourcefile_path isEqualToString: @"/full/path/to/source_file.m"]) {
                return;
            }

            NSString *compiler_path  = [obj valueForKey: @"_commandPath"];
            NSArray  *compiler_args  = [obj valueForKey: @"_arguments"];

            NSLog(@"\nCompile file '%@' with command line:\n%@ %@",
                  [sourcefile_path lastPathComponent], [compiler_path lastPathComponent],
                  [compiler_args componentsJoinedByString:@" "]);
        }
        // Linker arguments
        if ([[rule_info firstObject] isEqualToString: @"Ld"]) {
            /**
             * Xcode doesn't pass object files (.o) directly to the linker (it uses
             * a dependency info file instead) so the only thing we can do here is to grab
             * the linker arguments.
             */
            NSString *linker_path  = [obj valueForKey: @"_commandPath"];
            NSArray *linker_arguments = [obj valueForKey: @"_arguments"];

            NSLog(@"\nLinker: %@ %@", [linker_path lastPathComponent], [linker_arguments componentsJoinedByString: @" "]);
        }
    }];

That's all 🍰

And what if I want to get the compiler/linker arguments for every file in my project?

That's simple: the only difference between a solution for a one file is that you should iterate every single target (of every single project) you have:

id open_projects = [PBXProject performSelector: NSSelectorFromString: @"openProjects"];
[open_projects enumerateObjectsUsingBlock:^(id project, NSUInteger idx, BOOL *stop)
{
    id all_targets = [project valueForKey: @"_targets"];
    [all_targets enumerateObjectsUsingBlock:^(id target, NSUInteger idx, BOOL *stop)
    {
        id build_context = [target performSelector: NSSelectorFromString(@"targetBuildContext")];
                //
    }
}

So here we are (^^,)

Complete source code

#include <objc/runtime.h>

Class PBXReference = NSClassFromString(@"PBXReference");
id file_ref = [[PBXReference alloc] initWithPath: @"/full/path/to/source_file.m"];

Class PBXProject = NSClassFromString(@"PBXProject");
id suggested_targets = [PBXProject performSelector: NSSelectorFromString(@"targetsInAllProjectsForFileReference:justNative:")
                                        withObject: file_ref
                                        withObject: nil];
// just a first one
id target = suggested_targets[0];

id build_context = [target performSelector: NSSelectorFromString(@"targetBuildContext")];

NSArray *commands = [build_context performSelector: NSSelectorFromString(@"commands")];
[commands enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
    NSArray *rule_info = [obj performSelector: NSSelectorFromString(@"ruleInfo")];
    // Dump compiler arguments for the file
    if ([[rule_info firstObject] isEqualToString: @"CompileC"]) {
        /**
         * Commands can contain build rules for multiple file so 
         * make sure you got the right one.
         */
        NSString *sourcefile_path = [rule_info objectAtIndex: 2];
        if ( ! [sourcefile_path isEqualToString: @"/full/path/to/source_file.m"]) {
            return;
        }
        NSString *compiler_path  = [obj valueForKey: @"_commandPath"];
        NSArray  *compiler_args  = [obj valueForKey: @"_arguments"];

        NSLog(@"\nCompile file '%@' with command line:\n%@ %@",
              [sourcefile_path lastPathComponent], [compiler_path lastPathComponent],
              [compiler_args componentsJoinedByString:@" "]);
    }
    // Linker arguments
    if ([[rule_info firstObject] isEqualToString: @"Ld"]) {
        /*
         * Xcode doesn't pass object files (.o) directly to the linker (it uses
         * a dependency info file instead) so the only thing we can do here is to grab
         * the linker arguments.
         */
        NSString *linker_path  = [obj valueForKey: @"_commandPath"];
        NSArray *linker_arguments = [obj valueForKey: @"_arguments"];

        NSLog(@"\nLinker: %@ %@", [linker_path lastPathComponent], [linker_arguments componentsJoinedByString: @" "]);
    }
}];

@PaulTaykalo
Copy link
Member Author

Hi there. Will defenitely try it, once I get back to the Ukraine. Thank you :)

@PaulTaykalo
Copy link
Member Author

Hi there.
https://github.com/DyCI/dyci-main/tree/feature/xcode-plugin-compilation-flags/Support/Xcode

Playing now in different branch.

It seems that I finally can get correct target + compilation flags for selected file.

Now it's left to start compilation and

Linking..

Default linker params will not work - because we're not creating app binary again. We're creating dlyb which will contain only one file.

linkArgs = \
[xcodeLocation + "/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang-real"] \
+ ["-arch"] + [clangParams['arch']]\
+ ["-dynamiclib"]\
+ ["-isysroot"] + [clangParams['isysroot']]\
+ clangParams['LParams']\
+ clangParams['FParams']\
+ [clangParams['object']]\
+ ["-install_name"] + ["/usr/local/lib/" + libraryName]\
+ ['-Xlinker']\
+ ['-objc_abi_version']\
+ ['-Xlinker']\
+ ["2"]\
+ ["-ObjC"]\
+ ["-undefined"]\
+ ["dynamic_lookup"]\
+ ["-fobjc-arc"]\
+ ["-fobjc-link-runtime"]\
+ ["-Xlinker"]\
+ ["-no_implicit_dylibs"]\
+ [clangParams['minOSParam']]\
+ ["-single_module"]\
+ ["-compatibility_version"]\
+ ["5"]\
+ ["-current_version"]\
+ ["5"]\
+ ["-o"]\
+ [DYCI_ROOT_DIR + "/" + libraryName]

@PaulTaykalo
Copy link
Member Author

And there's still appCode...
:(

@PaulTaykalo
Copy link
Member Author

And here's the news.
There's no more need to use clang proxy in Xcode to do injections
https://github.com/DyCI/dyci-main/tree/feature/xcode-plugin-compilation-flags/Support/Xcode

Will try to check if there's something I can do to make it work in AppCode

@rodionovd
Copy link

Oh, that's great news, Paul 👍

About AppCode: can't we contact AppCode team directly? They don't seem to be as unreachable as Xcode guys, so let's take a chance (^^,)

@PaulTaykalo
Copy link
Member Author

Update here :)
So, I've decided to switch to .xcactivity files instead of playing with Xcode guts.
https://github.com/DyCI/xcactivity-parser
This is a script that will help me to do it :)
Not sure if it's more reliable than using runtime in Xcode, but at least, if something will brake, a bit more users will be able to fix it :)
Also this script is better in terms that almost any editor should be able find parameters to pass to this script

@PaulTaykalo PaulTaykalo changed the title There should be way to recompile files without clang proxying There should be way to recompile files without clang proxying Xcode May 28, 2015
@PaulTaykalo PaulTaykalo changed the title There should be way to recompile files without clang proxying Xcode There should be way to recompile files without clang proxying (Xcode) May 28, 2015
@PaulTaykalo
Copy link
Member Author

This is done for Xcode in #87

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants