A simple Cloud9 runner for a C program with multiple files

Last semester I taught a Data Structures course using C.  It was more complicated than I would have liked to create assignments and have students submit solutions.  I never got to what I would consider a good solution, but I’m going to try to record some of the things that helped here while they are still relatively fresh in my mind.

The first thing that I did was try to find a desktop solution that would work well on both Macintoshes and PCs.  This turned out to be untenable, so I moved to looking at cloud solutions.  I found that Cloud9 has a pretty slick setup that works perfectly well out of the box.  Just create a  c/c++ project and start hacking away at their sample main.c.  There is even a fairly decent debugger.  This solution uses cloud 9’s built-in runner that just builds and runs the currently active .c file.

But what happens when I want to create a project that contains multiple files?  The simple runner doesn’t have anything set up to compile more than one C file into an executable.  So, of course, the first pass is to start packing things into header files.  But I spent enough time deep in C compilers and debuggers back in the day that this seemed way to fragile.  And I was trying to establish good practices for the students.

So I took a look at the format of the runner file and found that it was reasonably easy to create a custom runner that builds all of the .c files in a directory into a program and correctly executes the program, even including attaching the debugger.

The documentation for custom runners is pretty sparse, but here’s the link to get you started:  https://docs.c9.io/docs/custom-runners.

Here is how I got past the limitations of a single source (.c) file.

Step 1:  Create a Test Program

Create a directory called “test-compound” and then added two files, main.c and aux.c into the directory.

main.c

#include <stdio.h>

extern void foo();

int main() {
    printf("Entering Main\n");
    foo();
    printf("Extiting Main\n");
    return 0;
}

aux.c

#include <stdio.h>

void foo() {
    printf("Foo is executing\n");
}

Now try running main.c using the C (simple) runner. You should get an error that looks like the following since the target program doesn’t include the code from aux.c.

Running "/home/ubuntu/workspace/test-compound/main.c"
/tmp/ccRmXnkw.o: In function `main':
main.c:(.text+0x14): undefined reference to `foo'
collect2: error: ld returned 1 exit status

Process exited with code: 1

Well, that’s no fun! So let’s see what we can do about it.

Step 2: Create a Custom Runner

You should have a tab in your run configurations (the bottom right view) that is titled test-compound/main.c.  This is where the error text showed up in step 1.  In the upper right side of the tab, you should see the text Runner: C (simple).  This is actually a button, so click on it to reveal a list of all of the built-in runner.  If you scroll all the way to the bottom of that list, you’ll see two additional options: New Runner and Edit Runner.

Choose Edit Runner, which will bring up a file titled C(simple).run that should look something like this:

// This file overrides the built-in C (simple) runner
// For more information see http://docs.c9.io:8080/#!/api/run-method-run
{
  "script": [
    "set -e",
    "if [ \"$debug\" == true ]; then ",
    "    gcc -ggdb3 -O0 -std=c99 $file -o \"$file\".o",
    "    chmod 755 $file.o",
    "    node $HOME/.c9/bin/c9gdbshim.js \"$file.o\" $args",
    "else",
    "    gcc -std=c99 $file -o $file.o",
    "    chmod 755 \"$file.o\"",
    "    \"$file.o\" $args",
    "fi"
  ],
  "info": "Running \"$file\"",
  "debugger": "gdb",
  "$debugDefaultState": false,
  "env": {},
  "selector": "^.*\\.c$"
}

Go to File->Save As and save the file as C (compound).run.  You can just save the file, but if you do this it will override the default behavior once you start modifying it, and that’s not the objective here.  You can choose Show Hidden Files from the directory tree setting (the gear next to the root folder) and open up the .c9->runners folder to see your new runner.

Step 3: Modify Your Runner

// This is a runner for multiple source (.c) files in a directory
// For more information see http://gramamoto.com/a-simple-cloud9-runner-for-a-c-program-with-multiple-files
{
  "script": [
    "set -e",
    "if [ \"$debug\" == true ]; then ",
    "    gcc -ggdb3 -O0 -std=c99  *.c -o \"$file_path/main.out\"",
    "    chmod 755 \"$file_path/main.out\"",
    "    node $HOME/.c9/bin/c9gdbshim.js \"$file_path/main.out\" $args",
    "else",
    "    gcc -std=c99 *.c -o \"$file_path/main.out\"",
    "    chmod 755 \"$file_path/main.out\"",
    "    \"$file_path/main.out\" $args",
    "fi"
  ],
  "info": "Running \"$file\"",
  "debugger": "gdb",
  "$debugDefaultState": false,
  "env": {},
  "selector": "^.*\\.c$"
}

Now when you have a .c file selected in a directory, you can choose Run->Run With->C (compound) and all the source (.c) files in the directory will be built into a single main.out, which will then be run.

Try this on the simple sample application I provided at the beginning of the post.  You should see something like this:

Running "/home/ubuntu/workspace/test-compound/main.c"
Testing...
Entering Main
Foo is executing
Exiting Main


Process exited with code: 0

And we have a successful run of a c program with two source files.  For homework assignments, you could provide a header file and a main.c with tests and have the student add additional source file(s) to complete the solution.

How to Remove Client Secrets from Source Code: Part II (the environment part)

Or:  How to batch update environment variables in Azure App Services.

It’s so easy, yet so wrong to put client secrets and passwords to external services in your source code.  But for a small site like music4dance, it just didn’t seem to be worth the trouble to do anything else when the only people that had access to the source code were also trusted enough to have access to the passwords.  I talked about why I changed my mind in my last post as well as how I refactored my code to make it reasonably easy to make the changes.

The other part of the problem is that I now have a couple of dozen environment variables that I want to set on a number of machines, both locally and in the cloud and I don’t want to have to update them manually.  I can easily write a command or PowerShell script to handle local machines, but how do I manage the Azure App Services in the cloud?  It seems like PowerShell remote management would be a reasonable way to approach this, similar to the solution to restarting Azure Search, but I was completely stumped on how to to do that.

Azure Resource Explorer

Until I found this new tool called Azure Resource Explorer.  Check out David Ebbo’s video describing it here:

Azure Resource Explorer

It’s incredibly straightforward.  Just open up the website https://resources.azure.com and sign in with the same credentials that you use for the Azure portal.  There is a drop-down control in the top center where you choose your directory, make sure you set it to the directory that you’re interested in modifying if you have multiple subscriptions set up.  Then take a gander at the tree control in the left pane.

I see two top-level nodes: Providers and Subscriptions. Spend some time browsing through this tree as there is plenty of gold here.

In order to change the environment variables for my web site, I’m looking for the settings for the App Service called music4dance which is housed in the Default-Web-WestUS resource group.  So I navigate through the hierarchy like this:  subscriptions->bizspark->resourceGroups->Default-Web-WestUS->providers->Microsoft.Web->sites->m4d->config->appsettings.

Once I’ve selected app settings in the tree, I can look at the main panel, where the default tab is labeled Data (GET, PUT).  The text box has a JSON formatted object that describes the node in the tree and contains a property set of the environment variables for app settings.  We can almost stop here if we want to.  The first time through this I just copied my block of environment variables into the properties section and hit the POST button.  I went back to the azure portal, navigated to the Application Settings panel and scrolled down to the app settings panel, and there I saw my new environment variables.

Azure Resource Explorer
Azure Resource Explorer

PowerShell

But the best part is the PowerShell tab. This gives the template to view and modify the object that we’re viewing.

# PowerShell equivalent script

# List appsettings
$resource = Invoke-AzureRmResourceAction -ResourceGroupName My-Resource-Group-Name -ResourceType Microsoft.Web/sites/config -ResourceName my-resource-name/appsettings -Action list -ApiVersion 2015-08-01 -Force
$resource.Properties

# SET list
$PropertiesObject = @{
	#Property = value;
}
New-AzureRmResource -PropertyObject $PropertiesObject -ResourceGroupName My-Resource-Group-Name -ResourceType Microsoft.Web/sites/config -ResourceName my-resource-name/appsettings -ApiVersion 2015-08-01 -Force

You can just copy this into a .ps file and add in your environment variables to the PropertyObject. Don’t forget to add all the other environment variables (you can grab these in an appropriate format from the get/post tab of the resource explorer). To match up with the code that I wrote to load the environment variables, my version of SET list looks something like this:

# SET list
$PropertiesObject = @{
    #Property = value;
    "xbox-client-id" = "my-xbox-clientid";
    "xbox-client-secret" = "my-xbox-client-secret";
    "spot-client-id", "my-spotify-clientid";
    "spot-client-secret", "my-spotify-client-secret";
    # and many more
}

Putting it all together

The way I run this script is to load it into the PowerShell ISE, log into the correct subscription (see the instruction to do that here) and run the script. Pretty simple, and not prone to typos or copy/paste errors the way doing the same thing in the azure portal would be.

I think the ultimate solution would be to write a pair of PowerShell scripts, one to do the local load and one to do the remote load, both taking the same tab separated or JSON formatted file of id/secret pairs. That’s more effort than I would put in for myself, but if others are interested I’ll see what I can throw together.