In the last few months twice I've wanted to access the source code of our application. The first time I did it I came up with a pretty neat hack, but it wouldn't really work in many places. The second time however, I asked the internet, and the internet replied.

TLDR: You can use your project's scheme to expose derived Xcode environment variables to your source code.

The rest of the blog post is a little bit about why I wanted to do that and what I did with it.

Both times I've wanted to access the source code of our apps is because I've wanted to make better admin tools. It should come as no surprise to people who know me that I care about tooling, but I also care a lot about making it possible for our admins to do their own thing. As such, our admin settings panel in Eigen is extensive.

Root React Components

The first time came when I started to think about what admin options I'd like to see for people using our React Native side. These are the options I came up with:

/images/source-code-sim/react-admin-eigen.png

There are two interesting things about it:

  • We support running any master commit of our React Native code inside Eigen, for Admins, via AppHub
  • We allow loading arbitrary React components as an admin.

It's this last bit that's interesting, right now I'm working on a new root Gene component (read: new view controller) in Emission, our React Native implementation. As this work has not moved upstream into Eigen, I can access it through a commit on AppHub, and then open it using our custom module loader:

/images/source-code-sim/react-module-eigen.png

In order to show the available root components (Artist/Home/Gene), we use GitHub's raw URLs to download the source code of our Open Source apps. Hah, a nice hack right? I created a ARAdminNetworkModel with an API like this:

1
2
3
4
5
6
7
@interface ARAdminNetworkModel : NSObject

- (void)getEmissionJSON:(NSString *)path completion:(void (^)(NSDictionary *json, NSError *error))completion;

- (void)getEmissionFile:(NSString *)path completion:(void (^)(NSString *fileContents, NSError *error))completion;

@end

Which simply uses NSURLSession under the hood:

1
2
3
4
5
6
7
8
9
10
11
- (void)getEmissionData:(NSString *)path completion:(void (^)(NSData *data, NSError *error))completion;
{
    NSURLSession *session = [NSURLSession sharedSession];
    NSString *urlFormat = @"https://raw.githubusercontent.com/artsy/emission/master/%@";
    NSString *url = [NSString stringWithFormat: urlFormat, path];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
            completion(data, error);
    }];
    [task resume];
}

Nothing special, but it required a cognitive jump to get there.

Submodule Introspection

The second time I wanted this is inside the example app for Emission. This is a typical example application for a library made by pod lib create. This example app is basically just the admin settings panel from Eigen, shown above.

When I switched the example app to use a similar theme and menu DSL as Eigen, I also took the chance to expand on the buttons we had available. Previously there was the ability to load the view controller for one specific artist, but I knew we had a giant list of artist slugs inside one of our optional sub-modules. What I wanted to do, was offer a random Artist from that if the submodule was init'd.

This required introspecting the source, which I could have also done via the GitHub API, but it was also feasible to do by accessing the filesystem outside of the simulator. This is totally possible ( and is how FBSnapshots works ) but I needed to access the project root, then I could build relative links. Thus, I asked the internet. I knew these variables existed, but that they were a part of the build process - and not exposed to the app runtime.

There are two ways to do it, both make sense for different contexts:

Now our scheme looks like this:

 
 

I can then use the value of SRCROOT as the start of an absolute path to get to any of the source code in our project. Making the final code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (ARCellData *)jumpToRandomArtist
{
  NSString *sourceRoot = [NSProcessInfo processInfo].environment[@"SRCROOT"];
  NSString *artistListFromExample = @"../externals/metaphysics/schema/artist/maps/artist_title_slugs.js";
  NSString *slugsPath = [sourceRoot stringByAppendingPathComponent:artistListFromExample];

  NSFileManager *manager = [NSFileManager defaultManager];

  // Don't have the submodule? bail, it's no biggie
  if (![manager fileExistsAtPath:slugsPath]) { return nil; }

  // Otherwise lets support jumping to a random Artist
  return [self tappableCellDataWithTitle:@"Artist (random from metaphysics)" selection: ^{
    NSString *data = [NSString stringWithContentsOfFile:slugsPath encoding:NSUTF8StringEncoding error:nil];

    ... and so on

Tooling

Paying attention to your admin tools, and improving your development experience for the whole team is a great way to win friends and influence people. Introspecting your source code may help that.

Categories: eigen, emission, ios, mobile